blob: 7f796bc6c5ed362a32740ddadd296f62abe16809 [file] [log] [blame]
/* Copyright 2015. Los Alamos National Security, LLC. This material was produced
* under U.S. Government contract DE-AC52-06NA25396 for Los Alamos National
* Laboratory (LANL), which is operated by Los Alamos National Security, LLC
* for the U.S. Department of Energy. The U.S. Government has rights to use,
* reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR LOS
* ALAMOS NATIONAL SECURITY, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR
* ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is modified
* to produce derivative works, such modified software should be clearly marked,
* so as not to confuse it with the version available from LANL.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Under this license, it is required to include a reference to this work. We
* request that each derivative work contain a reference to LANL Copyright
* Disclosure C15076/LA-CC-15-054 so that this work's impact can be roughly
* measured.
*
* This is LANL Copyright Disclosure C15076/LA-CC-15-054
*/
/*
* PowerParser is a general purpose input file parser for software applications.
*
* Authors: Chuck Wingate XCP-2 caw@lanl.gov
* Robert Robey XCP-2 brobey@lanl.gov
*/
// ***************************************************************************
// ***************************************************************************
// This class holds command lines broken up into words.
// The term command is used in a general sense, it includes variable
// assignments, do loops, usual commands, etc.
// ***************************************************************************
// ***************************************************************************
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <vector>
#include <deque>
#include <sstream>
#include <map>
#include <algorithm>
#include <math.h>
#include <assert.h>
#include "Parser_utils.hh"
#include "Variable.hh"
#include "Function.hh"
#include "Word.hh"
#include "Parser_math.hh"
#include "Cmd.hh"
namespace PP
{
using std::cout;
using std::endl;
using std::string;
using std::deque;
using std::vector;
using std::stringstream;
using std::pair;
using std::ifstream;
using std::ios;
using std::setw;
// index base, generally 1 for Fortran style and 0 for C/C++, default 1
static int index_base = 1;
static bool case_sensitive = false;
// ===========================================================================
// Default constructor.
// ===========================================================================
Cmd::Cmd()
{
init();
}
// ===========================================================================
// Constructor including map of variables.
// ===========================================================================
Cmd::Cmd(string s, map<string, Variable> *v, map<string, Function> *f,
deque<string> *lstr, int lnum, int file_lnum, string fname,
stringstream &serr, int &ierr)
{
init();
vmap = v;
fmap = f;
original_str = s;
line_number = lnum;
file_line_number = file_lnum;
filename = fname;
lines = lstr;
process_string(s, serr, ierr);
}
// ===========================================================================
// Add a word to this cmd.
// ===========================================================================
void Cmd::add_word(string str, int lnum, int file_lnum, string fname)
{
Word w(str, lnum, file_lnum, fname, lines);
words.push_back(w);
}
// ===========================================================================
// Erase a word from this cmd.
// ===========================================================================
void Cmd::erase_word(int iw)
{
words.erase(words.begin()+iw);
}
void Cmd::erase_last_word()
{
words.erase(words.begin()+(int)words.size()-1);
}
// ===========================================================================
// Remove words that are commas.
// ===========================================================================
void Cmd::remove_commas()
{
for (int i=0; i<(int)words.size(); i++) {
if (words[i].is_comma()) {
words.erase(words.begin()+i);
i -= 1;
}
}
}
// ===========================================================================
// Initialize various private data.
// ===========================================================================
void Cmd::init()
{
original_str = "";
//processed = false;
white_space = " \t";
delims = " \t()[],*/+-=!#";
vmap = NULL;
fmap = NULL;
line_number = 0;
file_line_number = 0;
filename = "";
}
// ===========================================================================
// Set index base for input file indexing. 1 -- Fortran like, 0 -- Other
// languages
// ===========================================================================
void Cmd::set_index_base(int base)
{
//cout << "Info:: Setting index base to " << base << endl;
index_base = base;
}
// ===========================================================================
// Set case sensitivity for input file commands.
// ===========================================================================
void Cmd::set_case_sensitive(bool case_sensitive_in)
{
case_sensitive = case_sensitive_in;
}
// ===========================================================================
// Process a string.
// Break the string into words and copy each word to a double ended queue.
// ===========================================================================
void Cmd::process_string(string in_str, stringstream &serr, int &ierr)
{
//cout << "&&&&& Original line = " << endl;
//cout << in_str << endl;
//cout << "01234567890123456789012345678901234567890123456789" << endl;
//cout << "0 1 2 3 4 " << endl;
string s;
int istart = 0;
bool found = false;
//int plevel = 0;
for (;;) {
delims = " \t()[],*/+-=!#";
// Extract the next word from the line.
found = extract_next_word(istart, in_str, s, serr, ierr);
if (!found) break;
// Create a new word using the word that was found.
// This removes quotes if there are any and types the word.
Word w(s, line_number, file_line_number, filename, lines);
// Copy the word to the end of the queue.
words.push_back(w);
}
// Set the command name and type.
reset_name_type();
}
// ===========================================================================
// Given a string, str, and a starting position in
// that string, istart, extract the next word and
// pass it back as a string.
// ===========================================================================
bool Cmd::extract_next_word(int &istart, string &str, string &word,
stringstream &serr, int &ierr)
{
// To suppress compiler warnings of unused parameters
//assert(serr == serr);
assert(ierr == ierr);
// If istart is out of bounds then there is nothing to do.
if (istart < 0) return false;
if (istart >= (int)str.size()) return false;
// Find the next non blank character.
int i1 = str.find_first_not_of(white_space, istart);
// If a non whitespace character was not found then there are no more
// words to extract.
if (i1 == (int)string::npos) return false;
// If the non blank character that was found is a delimiter, like
// ()[]+-/* ... then it needs to be a word by itself.
if (delims.find(str[i1], 0) != string::npos) {
word = str[i1];
istart = i1+1;
return true;
}
// At this point we have found the start of a word. The end of the
// word will be one of the delimiters like ()[]+=-*/spacetab ...
string wend = delims;
// A word delimited by quotes is handled differently. If the i1
// position in the string is a beginning quotes then we need to search
// for an ending quotes. Anything between quotes is part of the word
// including delimters.
bool quotes = false;
if (str[i1] == '"') {
quotes = true;
wend = "\"";
}
if (str[i1] == '\'') {
quotes = true;
wend = "\'";
}
// Search for the end of the word by finding the next delimiter. The
// delimiter is one index past the end of the word.
// But if the next delimiter is + or - then we have to consider that
// this could be a floating point number in which case we continue
// past the + or - to find the next delimiter.
int i2;
int i1_start = i1+1;
for(;;) {
i2 = str.find_first_of(wend, i1_start);
// If a delimiter was not found then the word extends to the end
// of the line.
if (i2 == (int)string::npos) {
i2 = str.size();
break;
}
else {
// Check for a floating point number (fpn). For example
// 1.34e+14 or -3.8E-19
// i2 might point to the + or - in e+14 or E-19, so we check
// for that case. Note that if the + or - is not found, then it
// could be a number like 1.e14 but then i2 would point to
// something after e14 and we would be ok.
// If we do find +e or -e, then everything in front of it needs
// to be a digit, if not then this is not a number.
bool fpn = false;
if (str[i2] == '+' || str[i2] == '-') {
if (str[i2-1] == 'e' || str[i2-1] == 'E' ||
str[i2-1] == 'd' || str[i2-1] == 'D') {
fpn = true;
for (int j=i1; j<=i2-2; j++) {
if (!isdigit(str[j]) && str[j] != '.') {
fpn = false;
break;
}
}
}
}
if (!fpn) break;
i1_start = i2+1;
}
}
// If the word is quoted then it should end in quotes.
// We do not check for quotes matching here because at this
// point we might be in a comment region where quotes mismatch
// is allowed. We check for quotes mismatch later.
if (quotes) {
if (i2 >= (int)str.size()) i2 = (int)str.size() - 1;
}
/*
if (quotes) {
bool missing = false;
if (i2 >= (int)str.size()) missing = true;
else if (str[i1] == '\"' && str[i2] != '\"') missing = true;
else if (str[i1] == '\'' && str[i2] != '\'') missing = true;
else if (str[i1] == '\"' && str[i2] == '\'') missing = true;
else if (str[i1] == '\'' && str[i2] == '\"') missing = true;
if (missing) {
fatal_error2(serr, ierr);
serr << "Quotes mismatch found." << endl;
serr << "A starting quotes must have a closing quotes." << endl;
serr << "Double quotes, \", must be matched with double quotes." << endl;
serr << "Single quotes, \', must be matched with single quotes." << endl;
ierr = 2;
return false;
}
}
*/
// We include the quotes symbols in the word. The quote symbols will
// be removed elsewhere.
if (quotes) i2 += 1;
// The word is now delimited by i1 and i2-1, return it in word.
word = str.substr(i1, i2 - i1);
// Update the starting point for finding the next word.
istart = i2;
// A word was successfully found so return true.
return true;
}
// ===========================================================================
// Reset the command name and type. Consider the following command:
// * lasdkj */ cmd = 5.0
// The original command name is "*", but after the multi-line comment is
// removed, the command name should be "cmd".
// ===========================================================================
void Cmd::reset_name_type()
{
if ((int)words.size() == 0) {
cmd_name = " ";
cmd_type = " ";
return;
}
cmd_name = words[0].get_string();
if (! case_sensitive) {
transform(cmd_name.begin(), cmd_name.end(), cmd_name.begin(), tolower);
}
cmd_type = "command";
if (words[0].is_variable()) cmd_type = "assignment";
if (cmd_name == "parser_list_variables") cmd_type = "debug";
if (cmd_name == "parser_list_functions") cmd_type = "debug";
if (cmd_name == "parser_print_fbuffer") cmd_type = "debug";
if (cmd_name == "if") cmd_type = "internal_cmd";
if (cmd_name == "elseif") cmd_type = "internal_cmd";
if (cmd_name == "endif") cmd_type = "internal_cmd";
if (cmd_name == "do") cmd_type = "internal_cmd";
if (cmd_name == "return") cmd_type = "internal_cmd";
if (cmd_name == "enddo") cmd_type = "internal_cmd";
if (cmd_name == "stop") cmd_type = "internal_cmd";
if (cmd_name == "when") cmd_type = "internal_cmd";
if (cmd_name == "endwhen") cmd_type = "internal_cmd";
}
// ===========================================================================
// Given a line like
// include filename1 filename2 filename3 ...
// Find the first filename that exists and return that.
// This should only be called on the io processor.
// ===========================================================================
string Cmd::get_cmd_filename(stringstream &ssfiles)
{
for (int i=1; i<(int)words.size(); i++) {
string fn = words[i].get_string();
// The quotes may still be on the word, strip them off if they are
// present.
int len = (int)fn.size();
if ((fn[len-1] == '\"') || (fn[len-1] == '\'')) {
fn.erase(fn.end() - 1);
}
if ((fn[0] == '\"') || (fn[0] == '\'')) {
fn.erase(fn.begin());
}
ssfiles << " " << fn << endl;
// Open the file to test if it exists.
ifstream instm(fn.c_str(), ios::in);
instm.close();
if( instm.fail() ) continue;
return fn;
}
return "";
}
// ===========================================================================
// Handle unary minus in a command line (not in math(..))
// ===========================================================================
void Cmd::handle_cmd_unary_minus(stringstream &serr, int &ierr)
{
int ipstart = 0;
for (;;) {
int ip = find(ipstart, (int)words.size()-1, "-");
// If we do not find any more minus signs then we are done.
if (ip == -1) return;
// The word after the minus sign must be a number.
if (!words[ip+1].is_number()) {
words[ip+1].fatal_error(serr, ierr);
serr << "Expected the object following the unary - to"
" be a number." << endl;
serr << "Instead, it was " << words[ip+1].get_string() << endl;
ierr = 2;
return;
}
// Actually do the negate operation.
do_unary_op(ip, "-");
ipstart = ip+1;
continue;
}
}
// ===========================================================================
// Handle unary plus in a command line (not in math(..))
// ===========================================================================
void Cmd::handle_cmd_unary_plus(stringstream &serr, int &ierr)
{
int ipstart = 0;
for (;;) {
int ip = find(ipstart, (int)words.size()-1, "+");
// If we do not find any more minus signs then we are done.
if (ip == -1) return;
// The word after the plus sign must be a number.
if (!words[ip+1].is_number()) {
words[ip+1].fatal_error(serr, ierr);
serr << "Expected the object following the unary + to"
" be a number." << endl;
serr << "Instead, it was " << words[ip+1].get_string() << endl;
ierr = 2;
return;
}
// The + sign is not needed.
delete_words(ip, ip);
ipstart = ip+1;
continue;
}
}
// ===========================================================================
// The following type of command is allowed:
// a(1) = 15*3.0
// meaning that 3.0 is to be replicated 15 times and thus a(1)-a(15) is set
// by this command.
// ===========================================================================
void Cmd::handle_cmd_multiplicity(stringstream &serr, int &ierr)
{
int ipstart = 0;
for (;;) {
int ip = find(ipstart, (int)words.size()-1, "*");
// If we do not find any more asterisks then we are done.
if (ip == -1) return;
if (ip==0) {
fatal_error2(serr, ierr);
serr << "Asterisk cannot be at the start of a line." << endl;
ierr = 2;
return;
}
if (ip == (int)words.size()-1) {
words[ip].fatal_error(serr, ierr);
serr << "Asterisk cannot be at the end of a line." << endl;
ierr = 2;
return;
}
// The word after the asterisk must be a number or a boolean.
// Wait, why is this? We actually allow strings also, really
// we allow anything.
//if (!words[ip+1].is_number() && !words[ip+1].is_bool()) {
// words[ip+1].fatal_error(serr, ierr);
// serr << "Expected the object following the * to"
// " be a number or a logical." << endl;
// serr << "Instead, it was " << words[ip+1].get_string() << endl;
// ierr = 2;
// return;
//}
// The word before the asterisk must be a number.
if (!words[ip-1].is_number()) {
words[ip-1].fatal_error(serr, ierr);
serr << "Expected the object before the * to"
" be a number." << endl;
serr << "Instead, it was " << words[ip-1].get_string() << endl;
ierr = 2;
return;
}
// Set the multiplicity.
int imult = words[ip-1].get_int(serr, ierr);
words[ip+1].set_multiplicity(imult);
Word w = words[ip+1];
replace_words(ip-1, ip+1, w);
ipstart = ip;
}
}
// ***************************************************************************
// ***************************************************************************
// Functions for getting values from the commands.
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Get boolean values. This gets all the words past the = sign,
// converts them to bool (and then to int), and puts them in the output arrays.
//
// The expected commands are:
// cmdname = .true. 0d
// cmdname(5) = false true false 1d
// cmdname(5,9) = true false true 2d
// etc.
//
// We also allow
// cmdname = false true false
// and we will supply the starting indices of (1) or (1,1), etc.
//
// But note that the , is gone at this point, so the 2d command is
// cmdname ( 5 9 ) = true false true 2d
//
// This function works for any dimension, 0,1,2,3,...
//
// We pass the result back as an int because of the incompatibility between
// fortran logical and c++ bool.
// ===========================================================================
void Cmd::get_bool_int(string &cname, int *array_vals, const vector<int> &size,
vector<Cmd *> &dup_cmd1, vector<int> &dup_wdex1,
int dup_fatal, vector<int> &dup_vals,
bool skip, stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
// Check syntax, for example an equals sign must be present, and set istart.
// istart Position in array_vals where we start filling it.
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
vector<int> istart(dim,0);
if (!check_syntax(istart, serr, ierr)) return;
// If skipping, we don't need to get array values.
if (skip) {
set_processed(true);
return;
}
// Get the number of values past the = sign.
// Also mark the words up to and including the = sign as processed.
int nvals = 0;
if (!get_nvals(istart, size, nvals, serr, ierr)) return;
// 0d is a special case.
if (dim == 0) {
bool b = words[2].get_bool(serr, ierr);
int cvalue = 0;
if (b) cvalue = 1;
*array_vals = cvalue;
return;
}
// Get the values and return.
//int ieqp1 = 5 + dim - 1;
int ieqp1 = find_equals() + 1;
Parser_utils putils(index_base);
int k = putils.start_dex(istart, size);
for (int i=ieqp1; i<(int)words.size(); i++) {
bool b = words[i].get_bool(serr, ierr);
int cvalue = 0;
if (b) cvalue = 1;
int imult = words[i].get_multiplicity();
for (int j=1; j<=imult; j++) {
error_dup_line(cname, i, k, dup_wdex1, dup_cmd1, dup_vals,
size, dup_fatal, serr, ierr);
array_vals[k++] = cvalue;
}
}
}
// ===========================================================================
// Get boolean values. This gets all the words past the = sign,
// converts them to bool and puts them in the output arrays.
//
// The expected commands are:
// cmdname = .true. 0d
// cmdname(5) = false true false 1d
// cmdname(5,9) = true false true 2d
// etc.
//
// We also allow
// cmdname = false true false
// and we will supply the starting indices of (1) or (1,1), etc.
//
// But note that the , is gone at this point, so the 2d command is
// cmdname ( 5 9 ) = true false true 2d
//
// This function works for any dimension, 0,1,2,3,...
// ===========================================================================
void Cmd::get_bool(string &cname, bool *array_vals, const vector<int> &size,
vector<Cmd *> &dup_cmd1, vector<int> &dup_wdex1,
int dup_fatal, vector<int> &dup_vals,
bool skip, stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
// Check syntax, for example an equals sign must be present, and set istart.
// istart Position in array_vals where we start filling it.
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
vector<int> istart(dim,0);
if (!check_syntax(istart, serr, ierr)) return;
// If skipping, we don't need to get array values.
if (skip) {
set_processed(true);
return;
}
// Get the number of values past the = sign.
// Also mark the words up to and including the = sign as processed.
int nvals = 0;
if (!get_nvals(istart, size, nvals, serr, ierr)) return;
// 0d is a special case.
if (dim == 0) {
bool b = words[2].get_bool(serr, ierr);
*array_vals = b;
return;
}
// Get the values and return.
//int ieqp1 = 5 + dim - 1;
int ieqp1 = find_equals() + 1;
Parser_utils putils(index_base);
int k = putils.start_dex(istart, size);
for (int i=ieqp1; i<(int)words.size(); i++) {
bool b = words[i].get_bool(serr, ierr);
int imult = words[i].get_multiplicity();
for (int j=1; j<=imult; j++) {
error_dup_line(cname, i, k, dup_wdex1, dup_cmd1, dup_vals,
size, dup_fatal, serr, ierr);
array_vals[k++] = b;
}
}
}
// ===========================================================================
// Get integer values. This gets all the words past the = sign,
// converts them to int, and puts them in the output arrays.
//
// The expected commands are:
// cmdname = some_int 0d
// cmdname(5) = 3, 5, -15, 10 1d
// cmdname(5,9) = 3, 7, -20, 154 2d
// etc.
//
// We also allow
// cmdname = 3, 5, -15, 10
// and we will supply the starting indices of (1) or (1,1), etc.
//
// But note that the , is gone at this point, so the 2d command is
// cmdname ( 5 9 ) = 3 7 -20 154 ...
//
// This function works for any dimension, 0,1,2,3,...
// ===========================================================================
void Cmd::get_int(string &cname, int *array_vals, const vector<int> &size,
vector<Cmd *> &dup_cmd1, vector<int> &dup_wdex1,
int dup_fatal, vector<int> &dup_vals,
bool skip, stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
// Check syntax, for example an equals sign must be present, and set istart.
// istart Position in array_vals where we start filling it.
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
vector<int> istart(dim,0);
if (!check_syntax(istart, serr, ierr)) return;
// If skipping, we don't need to get array values.
if (skip) {
set_processed(true);
return;
}
// Get the number of values past the = sign.
// Also mark the words up to and including the = sign as processed.
int nvals = 0;
if (!get_nvals(istart, size, nvals, serr, ierr)) return;
// 0d is a special case.
if (dim == 0) {
*array_vals = words[2].get_int(serr, ierr);
return;
}
// Get the values and return.
//int ieqp1 = 5 + dim - 1;
int ieqp1 = find_equals() + 1;
Parser_utils putils(index_base);
int k = putils.start_dex(istart, size);
for (int i=ieqp1; i<(int)words.size(); i++) {
int iw = words[i].get_int(serr, ierr);
int imult = words[i].get_multiplicity();
for (int j=1; j<=imult; j++) {
error_dup_line(cname, i, k, dup_wdex1, dup_cmd1, dup_vals,
size, dup_fatal, serr, ierr);
array_vals[k++] = iw;
}
}
}
// ===========================================================================
// Get int64_t values. This gets all the words past the = sign,
// converts them to int, and puts them in the output arrays.
//
// The expected commands are:
// cmdname = some_int 0d
// cmdname(5) = 3, 5, -15, 10 1d
// cmdname(5,9) = 3, 7, -20, 154 2d
// etc.
//
// We also allow
// cmdname = 3, 5, -15, 10
// and we will supply the starting indices of (1) or (1,1), etc.
//
// But note that the , is gone at this point, so the 2d command is
// cmdname ( 5 9 ) = 3 7 -20 154 ...
//
// This function works for any dimension, 0,1,2,3,...
// ===========================================================================
void Cmd::get_int(string &cname, int64_t *array_vals, const vector<int> &size,
vector<Cmd *> &dup_cmd1, vector<int> &dup_wdex1,
int dup_fatal, vector<int> &dup_vals,
bool skip, stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
// Check syntax, for example an equals sign must be present, and set istart.
// istart Position in array_vals where we start filling it.
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
vector<int> istart(dim,0);
if (!check_syntax(istart, serr, ierr)) return;
// If skipping, we don't need to get array values.
if (skip) {
set_processed(true);
return;
}
// Get the number of values past the = sign.
// Also mark the words up to and including the = sign as processed.
int nvals = 0;
if (!get_nvals(istart, size, nvals, serr, ierr)) return;
// 0d is a special case.
if (dim == 0) {
*array_vals = words[2].get_int64_t(serr, ierr);
return;
}
// Get the values and return.
//int ieqp1 = 5 + dim - 1;
int ieqp1 = find_equals() + 1;
Parser_utils putils(index_base);
int k = putils.start_dex(istart, size);
for (int i=ieqp1; i<(int)words.size(); i++) {
int64_t iw = words[i].get_int64_t(serr, ierr);
int imult = words[i].get_multiplicity();
for (int j=1; j<=imult; j++) {
error_dup_line(cname, i, k, dup_wdex1, dup_cmd1, dup_vals,
size, dup_fatal, serr, ierr);
array_vals[k++] = iw;
}
}
}
// ===========================================================================
// Get the real (double) values. This gets all the words past the = sign,
// converts them to doubles, and puts them in the output arrays.
//
// The expected commands are:
// cmdname = some_double 0d
// cmdname(5) = 3.0, 35, -15e20, 10.154 1d
// cmdname(5,9) = 3.0, 35, -15e20, 10.154 2d
// etc.
//
// We also allow
// cmdname = 3.0, 35, -15e20, 10.154
// and we will supply the starting indices of (1) or (1,1), etc.
//
// But note that the , is gone at this point, so the 2d command is
// cmdname ( 5 9 ) = 3.0 35 -15e20 10.154 ...
//
// This function works for any dimension, 0,1,2,3,...
// ===========================================================================
void Cmd::get_real(string &cname, double *array_vals, const vector<int> &size,
vector<Cmd *> &dup_cmd1, vector<int> &dup_wdex1,
int dup_fatal, vector<int> &dup_vals,
bool skip, stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
// Check syntax, for example an equals sign must be present, and set istart.
// istart Position in array_vals where we start filling it.
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
vector<int> istart(dim,0);
if (!check_syntax(istart, serr, ierr)) return;
// If skipping, we don't need to get array values.
if (skip) {
set_processed(true);
return;
}
// Get the number of values past the = sign.
// Also mark the words up to and including the = sign as processed.
int nvals = 0;
if (!get_nvals(istart, size, nvals, serr, ierr)) return;
// 0d is a special case.
// Note that we do not increment dup_vals for 0d because duplicate scalar
// commands are handled differently from array commands.
if (dim == 0) {
*array_vals = words[2].get_double(serr, ierr);
return;
}
// All other dimensions.
//int ieqp1 = 5 + dim - 1;
int ieqp1 = find_equals() + 1;
Parser_utils putils(index_base);
int k = putils.start_dex(istart, size);
for (int i=ieqp1; i<(int)words.size(); i++) {
double d = words[i].get_double(serr, ierr);
int imult = words[i].get_multiplicity();
for (int j=1; j<=imult; j++) {
error_dup_line(cname, i, k, dup_wdex1, dup_cmd1, dup_vals,
size, dup_fatal, serr, ierr);
array_vals[k++] = d;
}
}
}
// ===========================================================================
// Get the character values. This gets all the words past the = sign,
// converts them to chars, and puts them in the output arrays.
//
// The expected commands are:
// cmdname = q 0d single character
// cmdname = char_string 0d character string
// cmdname(3) = "May" "the", "force", "be" 1d array of strings
// cmdname(5,9) = "11" "21" "31" 2d
// etc.
//
// We also allow
// cmdname = "May" "the", "force", "be"
// and we will supply the starting indices of (1) or (1,1), etc.
//
// But note that the , is gone at this point, so the 2d command is
// cmdname ( 5 9 ) = "11" "21" "31"
//
// This function works for any dimension, 0,1,2,3,...
// For 0d, it has an extra flag to distinguish between single characters
// and a character string.
// ===========================================================================
void Cmd::get_char(string &cname, vector<string> &vstr, const vector<int> &size,
bool single_char, vector<Cmd *> &dup_cmd1,
vector<int> &dup_wdex1, int dup_fatal,
vector<int> &dup_vals, bool skip,
stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
// Check syntax, for example an equals sign must be present, and set istart.
// istart Position in array_vals where we start filling it.
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
vector<int> istart(dim,0);
if (!check_syntax(istart, serr, ierr)) return;
// If skipping, we don't need to get array values.
if (skip) {
set_processed(true);
return;
}
// Get the number of values past the = sign.
// Also mark the words up to and including the = sign as processed.
int nvals = 0;
if (!get_nvals(istart, size, nvals, serr, ierr)) return;
// 0d is a special case - get a single char
if (dim == 0 && single_char) {
vstr[0] = words[2].get_single_char(serr, ierr);
return;
}
// 0d is a special case - get a single string
if (dim == 0) {
vstr[0] = words[2].get_stringp();
return;
}
// Get the value and return, dim > 0.
// get_stringp is the same as get_string except get_stringp also marks
// the word as being processed.
//int ieqp1 = 5 + dim - 1;
int ieqp1 = find_equals() + 1;
Parser_utils putils(index_base);
int k = putils.start_dex(istart, size);
for (int i=ieqp1; i<(int)words.size(); i++) {
string s = words[i].get_stringp();
int imult = words[i].get_multiplicity();
for (int j=1; j<=imult; j++) {
error_dup_line(cname, i, k, dup_wdex1, dup_cmd1, dup_vals,
size, dup_fatal, serr, ierr);
vstr[k++] = s;
}
}
}
// ===========================================================================
// Get sizes of arrays, this works for dimensions 1,2,3,...
//
// The size vector contains the sizes (or bounds) of each array dimensions.
// It is assumed in this routine that all but the last size is known (this
// is input) and that this routine will determine the last size. See the
// get_sizeb function below where a different assumption is made.
//
// Suppose, for example, we have a 3d array called a3d which is dimensioned
// a3d(5,3,:). The first two dimensions are known, 5 and 3, the last dimension
// is unknown and will be determined by this routine.
//
// size is a vector of ints of size 3, with elements 5,3,? where the ? is
// to be determined.
//
// Note that this routine is called in a loop over all the lines in the
// input which is why we set the size using maximum.
// ===========================================================================
void Cmd::get_size(vector<int> &size, stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
// Check syntax, also sets istart.
// istart Position in array where we start filling it.
// Example command might be a3d(3,2,2) = ... In this case istart
// would be a vector of length 3 contining 3,2,2
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
vector<int> istart(dim,0);
if (!check_syntax(istart, serr, ierr)) return;
// Get the number of values past the = sign.
int nvals = 0;
vector<int> size0(dim,0);
if (!get_nvals(istart, size0, nvals, serr, ierr)) return;
int sm = 1;
for (int i=0; i<dim-1; i++) {
sm *= size[i];
}
// Set the size.
int maxval = istart[dim-1] + (nvals-1)/sm;
if (maxval > size[dim-1]) {
size[dim-1] = maxval;
}
}
// ===========================================================================
// This is a special purpose routine to get sizes for certain 2d arrays.
//
// Suppose we have the following input
// mults(1,1) = 0. 0. 1. 5. 6. 9.
// mults(1,2) = 3. 5. 8. 9. 10. 11. 20. 10
// mults(1,3) = 30. 5. 38. 3.
// In this case we don't know the size of either of the array dimensions, and
// of course the user does not know the size either and thus cannot somehow
// merge the above two lines.
//
// The purpose of this function is to obtain sizes for both the array
// dimensions so memory allocation of the array can be done.
//
// The size vector contains the sizes (or bounds) of each array dimensions.
// For the above example, this function would determine size[0] to be 8 and
// size[1] to be 3. 8 is just the max of the number of values put in per
// entry and 3 is just the max of the second index.
//
// Note that this routine is called in a loop over all the lines in the
// input which is why we set the size using maximum.
//
// This routine only works for 2d arrays.
// ===========================================================================
void Cmd::get_sizeb(vector<int> &size, stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
// This is a special purpose routine, dim must be 2.
if (dim != 2) {
fatal_error2(serr, ierr);
serr << "Cmd.cc, get_sizeb, internal error." << endl;
serr << "dim != 2, dim=" << dim << endl << endl;
ierr = 2;
return;
}
// Check syntax, also sets istart.
// istart Position in array where we start filling it.
// Example command might be a3d(3,2,2) = ... In this case istart
// would be a vector of length 3 contining 3,2,2
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
vector<int> istart(dim,0);
if (!check_syntax(istart, serr, ierr)) return;
// Get the number of values past the = sign.
int nvals = 0;
vector<int> size0(dim,0);
if (!get_nvals(istart, size0, nvals, serr, ierr)) return;
// Set the size vector
int maxval = istart[0] + nvals - 1;
if (maxval > size[0]) {
size[0] = maxval;
}
maxval = istart[1];
if (maxval > size[1]) {
size[1] = maxval;
}
}
// ===========================================================================
// Check command syntax for any dimension array. The expected command is:
// cmdname = .true. 0d
// cmdname(5) = 1, 3, -4 1d
// cmdname(3,4) = 1.e19, 23., -45. 2d
// etc.
//
// We also allow
// cmdname = "May" "the", "force", "be"
// and we will supply the starting indices of (1) or (1,1), etc.
//
// Note that at this point, the commas have been removed so the 2d command
// is actually
// cmdname ( 3 4 ) = 1.e19 23. -45.
// ===========================================================================
bool Cmd::check_syntax(vector<int> &istart, stringstream &serr, int &ierr)
{
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)istart.size();
bool skip_check = false;
if (dim > 0) {
int ieqt = find_equals();
if (ieqt == 1) skip_check = true;
}
// Must be at least a certain number of words on the line.
int nw_min = 3;
int nw_min_wc = 3;
if (dim > 0 && (!skip_check)) {
nw_min = 6 + dim - 1;
nw_min_wc = nw_min + dim - 1;
}
if ((int)words.size() < nw_min) {
fatal_error2(serr, ierr);
serr << "Expected number words in this line >= " << nw_min_wc << endl;
serr << "Actual number words = " << words.size() << endl << endl;
ierr = 2;
// If there aren't enough words on the line, then it is hopeless.
return false;
}
// Word at index ieq must be an = sign.
int ieq = 1;
int ieq_wc = 2;
if (dim > 0 && (!skip_check)) {
ieq = 4 + dim -1;
ieq_wc = ieq + 1 + dim - 1;
}
if (words[ieq].get_string() != "=") {
words[ieq].fatal_error(serr, ierr);
serr << "Expected an equals sign for symbol " << ieq_wc << endl;
serr << "Instead symbol " << ieq_wc << " is: " <<
words[ieq].get_string() << endl << endl;
ierr = 2;
}
// The value must not have any multiplicity, i.e. be just a single value.
// This only applies to 0d, values for arrays can have multiplicity.
if (dim == 0) {
if (words[2].get_multiplicity() != 1) {
words[2].fatal_error(serr, ierr);
serr << "Multiplicity not equal 1 for " << words[2].get_string() << endl;
serr << "Multiplicity is: " << words[2].get_multiplicity() << endl << endl;
ierr = 2;
}
}
// Nothing more to check for 0d.
if (dim == 0) return true;
if (!skip_check) {
// Word at index 1 must be a "(".
if (words[1].get_string() != "(") {
words[1].fatal_error(serr, ierr);
serr << "Expected an open parenthesis ,(, following the command name"
" in this line," << endl;
serr << "For example: " << cmd_name << "(...) = ..." << endl;
serr << "Instead found: " << words[1].get_string() << endl << endl;
ierr = 2;
}
// There must be a closing parenthses.
int irp = 3 + dim - 1;
if (words[irp].get_string() != ")") {
words[irp].fatal_error(serr, ierr);
serr << "Expected a close parenthesis ,), following the array indices"
" in this line," << endl;
serr << "For example: " << cmd_name << "(...) = ..." << endl;
serr << "Instead found: " << words[irp].get_string() << endl << endl;
ierr = 2;
}
}
// istart Position in array_vals where we start filling it.
// Note that istart starts from index base (default 1, Fortran style)
// Use set_index_base_zero for C/C++ index convention
if (skip_check) {
for (int i=0; i<dim; i++) {
istart[i] = index_base;
}
}
else {
int ierr2 = 0;
for (int i=0; i<dim; i++) {
int iloc = 2 + i;
istart[i] = words[iloc].get_int(serr, ierr);
if (ierr < 2 && istart[i] < index_base) {
words[iloc].fatal_error(serr, ierr);
serr << "The index for the array must be an integer that is >= " << index_base << endl;
serr << "Integer includes numbers like 3, 3., 3.0, but not 3.5" << endl;
serr << "The index input is: " << istart[i] << endl << endl;
ierr2 = 2;
}
}
if (ierr2 == 2) ierr = 2;
}
for (int i=0; i<dim; i++) {
if (istart[i] < index_base) return false;
}
return true;
}
// ===========================================================================
// Get the number of values (nvals) past the = sign.
// Check that nvals does not exceed array size.
// Also mark the words up to and including the = sign as processed.
//
// Works for any array dimension = 0,1,2,3,...
// ===========================================================================
bool Cmd::get_nvals(vector<int> &istart, const vector<int> &size,
int &nvals, stringstream &serr, int &ierr)
{
int nvals_cur;
// Get the array dimension, 0,1,2,3,...
int dim = (int)istart.size();
// 0d is a special case.
if (dim == 0) {
nvals = 1;
//if (size[0] == 0) return true;
words[0].set_processed(true);
words[1].set_processed(true);
return true;
}
// Index of word after equals sign.
//int ieqp1 = 5 + dim - 1;
int ieqp1 = find_equals() + 1;
// nvals Number of values after the = sign.
nvals = 0;
for (int i=ieqp1; i<(int)words.size(); i++) {
nvals_cur = words[i].get_multiplicity();
if( nvals_cur <= 0 ){
fatal_error2(serr, ierr);
serr << "Count must be positive [" << nvals_cur << "]" << endl;
ierr = 2;
}
nvals += nvals_cur;
}
// This is for the get size function. We just want nvals and do not want
// to do the check or marking as processed.
if (size[0] == 0) return true;
// Get the max size of the array.
//int maxvals = size1*size2;
int maxvals = size[0];
for (int i=2; i<=dim; i++) {
maxvals *= size[i-1];
}
//int ip = istart[0]-1;
//if (dim == 2) {
// ip = istart[0]-1 + (istart[1]-1)*size[0];
//}
//if (dim == 3) {
// ip = istart[0]-1 + (istart[1]-1)*size[0] + (istart[2]-1)*size[0]*size[1];
//}
//if (dim == 4) {
// ip = istart[0]-1 + (istart[1]-1)*size[0] + (istart[2]-1)*size[0]*size[1] +
// (istart[3]-1)*size[0]*size[1]*size[2];
//}
//ip += nvals - maxvals;
// Find the excess, i.e. the max array position the user is trying to
// fill compared with the max size allowed.
Parser_utils putils(index_base);
int ix = putils.start_dex(istart, size);
int excess = ix + nvals - maxvals;
//cout << "&&&&&cw ip = " << ip << " excess = " << excess << endl;
// Check that the number of values input by the user does not exceed
// the array size.
//int excess = istart1 - 1 + (istart2-1)*size1 + nvals - maxvals;
if (excess > 0) {
fatal_error2(serr, ierr);
serr << "Maximum number of values allowed = " << maxvals << endl;
serr << "(for multi-dimension arrays this max number is" << endl;
serr << " max_dim1 * max_dim2 * ...)" << endl;
serr << "This command exceeds that value by " <<
excess << endl << endl;
ierr = 2;
}
// If fatal errors, then do not attempt further processing.
if (ierr == 2) return false;
// Mark as processed.
for (int i=0; i<ieqp1; i++) {
words[i].set_processed(true);
}
return true;
}
// ***************************************************************************
// ***************************************************************************
// Handle variables.
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Check for the user defining variable dimensions for multi-dimensional
// arrays.
//
// This function works for any dimension, 0,1,2,3,...
// ===========================================================================
bool Cmd::check_for_dimension(stringstream &serr, int &ierr)
{
// First do some checks.
string varname = words[0].get_string();
if (!words[0].is_variable()) return false;
for (int i=0; i<(int)words.size(); i++) {
if (words[i].get_string() == "=") {
return false;
}
}
if (words[1].get_string() != "dimension") return false;
// Find the variable, if not found, then create it.
map<string, Variable>::iterator p;
p = vmap->find(varname);
if (p == vmap->end()) {
Variable v(varname);
vmap->insert(pair<string, Variable>(v.get_varname(), v));
}
p = vmap->find(varname);
// Extract the bounds from the line.
vector<int> bounds;
for (int i=2; i<(int)words.size(); i++) {
if (words[i].get_string() == "(") continue;
if (words[i].is_comma()) continue;
if (words[i].get_string() == ":") continue;
if (words[i].get_string() == ")") break;
// Get the bounds, note that this also makes sure it is an integer.
bounds.push_back(words[i].get_int(serr, ierr));
}
// Actually set the bounds for the variable.
int lnum = words[0].get_line_number();
int file_lnum = words[0].get_file_line_number();
string fname = words[0].get_filename();
p->second.set_bounds(bounds, lnum, file_lnum, fname,
lines, serr, ierr);
return true;
}
// ===========================================================================
// Check for the command
// variable_description variable_name description
// If found, then set the description for the variable.
// Create the variable if necessary.
// ===========================================================================
bool Cmd::check_for_var_description(stringstream &serr, int &ierr)
{
if (words[0].get_string() != "variable_description") return false;
// Must be 3 words in the line.
if (words.size() != 3) {
words[0].fatal_error(serr, ierr);
serr << "The variable_description command must have 3 words on the"
" line" << endl;
serr << "First word = variable_description" << endl;
serr << "Second word = name of the variable" << endl;
serr << "Third word = description (usually some phrase in quotes)" << endl;
serr << "This command has " << words.size() <<
" words instead of 3 words." << endl;
ierr = 2;
return true;
}
// The variable name is word 1.
string varname = words[1].get_string();
if (!words[1].is_variable()) {
words[0].fatal_error(serr, ierr);
serr << "Expected a variable name as word 2" << endl;
serr << "Variable names must begin with the $ character." << endl;
serr << "This variable name does not begin with a $ character." << endl;
serr << "Note that putting quotes around a variable name makes it" << endl;
serr << "a string, not a variable." << endl;
serr << "Variable name = " << varname << endl;
ierr = 2;
return true;
}
// Get the description.
string vardes = words[2].get_string();
// Find the variable, if not found, then create it.
map<string, Variable>::iterator p;
p = vmap->find(varname);
if (p == vmap->end()) {
Variable v(varname);
vmap->insert(pair<string, Variable>(v.get_varname(), v));
}
p = vmap->find(varname);
// Cannot change pre-defined variables.
if (p->second.is_pre_defined()) {
words[0].fatal_error(serr, ierr);
serr << "Cannot change the description for a pre-defined"
" variable" << endl;
serr << "Variable name = " << varname << " is pre-defined." << endl;
ierr = 2;
return true;
}
// Actually set the description.
p->second.set_description(vardes);
return true;
}
// ===========================================================================
// Go through each word on the line (starting after the equals sign if
// present), and replace each variable with its value.
// This is for scalar variables only.
// ===========================================================================
void Cmd::substitute_variables(stringstream &serr, int &ierr)
{
int irstart = 0;
for (int i=0; i<(int)words.size(); i++) {
if (words[i].get_string() == "=") {
irstart = 1;
break;
}
}
int nw1 = (int)words.size()-1;
subvar_w0(irstart, nw1, serr, ierr);
}
// ===========================================================================
// Scan words i1 through i2 inclusive, replace any variables found with
// their value.
// If the variable is followed by ++ or --, handle that also.
// This is for scalar variables only.
// ===========================================================================
void Cmd::subvar_w0(int i1, int &i2, stringstream &serr, int &ierr)
{
for (int i=i1; i<=i2; i++) {
string s = words[i].get_string();
if (words[i].is_variable()) {
int increment = 0;
if (i < i2) {
string ppmm = words[i+1].get_string();
if (ppmm == "++") increment = 1;
if (ppmm == "--") increment = -1;
}
subvar0(i, s, increment, serr, ierr);
if (increment != 0) {
delete_words(i+1,i+1);
i2 -= 1;
}
}
}
}
// ===========================================================================
// Given a variable name, varname, and its index in the words array, vardex,
// replace it with its value.
// This is for scalar variables only.
// ===========================================================================
void Cmd::subvar0(int vardex, string &varname, int increment,
stringstream &serr, int &ierr)
{
vector<int> adex;
map<string, Variable>::iterator p;
p = vmap->find(varname);
if (p != vmap->end()) {
int lnum = words[vardex].get_line_number();
int file_lnum = words[vardex].get_file_line_number();
string fname = words[vardex].get_filename();
string svalue = p->second.get_var_value(adex, words[vardex].get_string(),
lnum, file_lnum, fname,
lines, serr, ierr);
//int increment = words[vardex].get_increment();
if (increment != 0) p->second.bump_var(adex, increment,
lnum, file_lnum, fname,
lines, serr, ierr);
//words[vardex].set_increment(0);
words[vardex].set_value(svalue);
}
else {
// The variable has not been defined yet.
words[vardex].fatal_error(serr, ierr);
serr << "Attempted to use a variable before it was defined."
<< endl;
serr << "Undefined variable = " << varname << endl;
ierr = 2;
}
}
// ===========================================================================
// Store the variable value(s), define if needed.
// Examples:
// $radius = 3.0 0d
// $radius(1) = 3.0 4. 5.6e19 1d
// $radius(3,4) = 3.0 4. 5.6e19 4 5 9 2d
// ...
//
// This function works for any dimension, 0,1,2,3,...
// ===========================================================================
void Cmd::set_variables(stringstream &serr, int &ierr)
{
//cout << "&&&&&cw Enter set_variables, words[0] = " << words[0].get_string() << endl;
int ieq = -1;
for (int i=0; i<(int)words.size(); i++) {
if (words[i].get_string() == "=") {
ieq = i;
break;
}
}
// If an equals sign was not found on the line, then this is not a
// variable assignment.
if (ieq == -1) return;
// If the first character of the first word is not a $, then this is not
// a variable assignment.
string vname = words[0].get_string();
if (!words[0].is_variable()) return;
// Define a few common things.
int lnum = words[0].get_line_number();
int file_lnum = words[0].get_file_line_number();
string fname = words[0].get_filename();
vector<string> valvec;
int dim = 0;
if (ieq >= 4) dim = ieq - 3;
//cout << "&&&&&cw vname=" << vname << " ieq=" << ieq << " dim=" << dim << endl;
// Do some checking.
if (dim == 0) {
// Must be 3 words in the line (for example: $radius = 3.0)
if (words.size() != 3) {
fatal_error2(serr, ierr);
serr << "Expected number words in this line = 3" << endl;
serr << "Actual number words = " << words.size() << endl << endl;
ierr = 2;
// If there aren't enough words on the line, then it is hopeless.
if (words.size() < 3) return;
}
// The value must not have any multiplicity, i.e. be just a single value.
// This only applies to 0d, values for arrays can have multiplicity.
if (words[2].get_multiplicity() != 1) {
words[2].fatal_error(serr, ierr);
serr << "Multiplicity not equal 1 for " << words[2].get_string() << endl;
serr << "Multiplicity is: " << words[2].get_multiplicity() << endl << endl;
ierr = 2;
}
}
if (dim > 0) {
int nw_min = dim + 5;
int nw_min_wc = nw_min + dim - 1;
if ((int)words.size() < nw_min) {
fatal_error2(serr, ierr);
serr << "Expected number of symbols in this line >= " << nw_min_wc << endl;
serr << "Actual number of symbols is less than expected." << endl << endl;
ierr = 2;
// If there aren't enough words on the line, then it is hopeless.
return;
}
// Word at index 1 must be a "(".
if (words[1].get_string() != "(") {
words[1].fatal_error(serr, ierr);
serr << "Expected an open parenthesis ,(, following the variable name"
" in this line," << endl;
serr << "For example: " << vname << "(...) = ..." << endl;
serr << "Instead found: " << words[1].get_string() << endl << endl;
ierr = 2;
return;
}
// There must be a closing parenthses.
int irp = 3 + dim - 1;
if (words[irp].get_string() != ")") {
words[irp].fatal_error(serr, ierr);
serr << "Expected a close parenthesis ,), following the array indices"
" in this line," << endl;
serr << "For example: " << vname << "(...) = ..." << endl;
serr << "Instead found: " << words[irp].get_string() << endl << endl;
ierr = 2;
return;
}
}
// Store the values in a vector.
for (int i=ieq+1; i<(int)words.size(); i++) {
int imult = words[i].get_multiplicity();
string s = words[i].get_string();
for (int j=1; j<=imult; j++) {
valvec.push_back(s);
}
}
// Store the array indices in a vector.
vector<int> istart(dim,0);
int ierr2 = 0;
for (int d=0; d<dim; d++) {
istart[d] = words[d+2].get_int(serr, ierr);
if (istart[d] <= 0) {
words[d+2].fatal_error(serr, ierr);
serr << "The index for the array must be an integer that is >= " << index_base << endl;
serr << "Integer includes numbers like 3, 3., 3.0, but not 3.5" << endl;
serr << "The index input is: " << istart[d] << endl << endl;
ierr2 = 2;
}
}
if (ierr2 == 2) {
ierr = 2;
return;
}
// Find the variable name in the variable map.
map<string, Variable>::iterator p;
p = vmap->find(vname);
// If the variable is found in the variable map, then replace
// its value with the new value. If the variable is not found
// in the variable map, then add it as a new variable.
if (p != vmap->end()) {
p->second.set_var_value(istart, valvec, lnum, file_lnum, fname,
lines, serr, ierr);
}
else {
Variable v(vname, istart, valvec, lnum, file_lnum, fname, lines,
serr, ierr);
vmap->insert(pair<string, Variable>(v.get_varname(), v));
}
}
// ===========================================================================
// Evaluate a variable.
// We have a word followed by multiple arguments. Find out if the word is
// a variable, use the arguments to get the variable value, replace the
// variable and arguments with its value.
//
// This function works for any dimension, 0,1,2,3,...
// ===========================================================================
bool Cmd::evaluate_variable(int iw1, int &i2, int &nargs,
stringstream &serr, int &ierr)
{
// If there is no map of variables, then we do nothing.
if (vmap == NULL) return false;
// Do nothing if the word is not a variable (begins with $).
if (!words[iw1].is_variable()) return false;
// Get the variable name.
string varname = words[iw1].get_string();
// Find the variable.
map<string, Variable>::iterator p;
p = vmap->find(varname);
// The variable was not found.
if (p == vmap->end()) {
words[iw1].fatal_error(serr, ierr);
serr << "Trying to use a variable before it is defined." << endl;
serr << "Undefined variable = " << varname << endl;
serr << "The list of defined variables (at this point) is:" << endl;
for (p=vmap->begin(); p!=vmap->end(); p++) {
serr << p->second.get_varname() << endl;
}
ierr = 2;
return true;
}
// The variable was found, do the evaluation and replace the words.
// Check to see if all the variable arguments have a value.
bool has_value = true;
for (int i=0; i<nargs; i++) {
int j = iw1 + 1 + i;
if (!words[j].is_number()) {
has_value = false;
words[j].fatal_error(serr, ierr);
serr << "Expected a number for variable index " <<
i+1 << endl;
serr << "Instead found: " << words[j].get_string() << endl;
ierr = 2;
}
}
if (!has_value) return true;
// Check to see if all the variable arguments are integer.
bool all_int = true;
for (int i=0; i<nargs; i++) {
int j = iw1 + 1 + i;
if (!words[j].is_integer()) {
all_int = false;
words[j].fatal_error(serr, ierr);
serr << "Expected an integer for variable index " <<
i+1 << endl;
serr << "Instead found: " << words[j].get_string() << endl;
ierr = 2;
}
}
if (!all_int) return true;
// All the indices have values and are ints.
// Load all the arguments into a vector of ints.
vector<int> vdex;
for (int i=0; i<nargs; i++) {
int j = iw1 + 1 + i;
int iv = words[j].get_int();
vdex.push_back(iv);
}
// Check for ++ or -- following the variable.
int increment = 0;
int ippmm = iw1 + nargs + 1;
if (ippmm <= i2 && ippmm <(int)words.size()) {
string sppmm = words[ippmm].get_string();
if (sppmm == "++") increment = 1;
if (sppmm == "--") increment = -1;
}
// Evaluate the variable and replace the words.
// Works in any dimensionality.
int ln = words[iw1].get_line_number();
int file_ln = words[iw1].get_file_line_number();
string fname = words[iw1].get_filename();
string result = p->second.get_var_value(vdex, varname, ln, file_ln,
fname, lines, serr, ierr);
//int increment = words[iw1].get_increment();
if (increment != 0) p->second.bump_var(vdex, increment, ln, file_ln,
fname, lines, serr, ierr);
//words[iw1].set_increment(0);
Word w(result, ln, file_ln, fname, lines);
replace_words(iw1, iw1+nargs, w);
i2 -= nargs;
// If the variable was followed by a ++ or --, then remove the
// ++ or -- since it has been used.
if (increment != 0) {
delete_words(iw1+1, iw1+1);
i2 -= 1;
}
return true;
}
// ***************************************************************************
// ***************************************************************************
// Math evaluation.
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Math evaluation driver.
// ===========================================================================
void Cmd::math_eval(stringstream &serr, int &ierr)
{
// Combine * * into **, i.e. form the exponentiation operator.
handle_star_star();
// Ops like .and., .eq., ... can at this point be part of larger words,
// they need to be extracted as individual words.
handle_ops();
//cout << "&&&&&cw Cmd.cc, Enter math_eval" << endl;
//for (int i=0; i<(int)words.size(); i++) {
// cout << words[i].get_string() << endl;
//}
int ieq = find_any_char(0, (int)words.size()-1, "=");
bool ifcmd = false;
if (words[0].get_string() == "if") {
ifcmd = true;
ieq = -1;
}
for (int i=0; i<(int)words.size(); i++) {
if (words[i].get_string() == "(" /*&& i>ieq*/) {
if (ifcmd && i>1) continue;
if (i > (int)words.size()-2) {
words[i].fatal_error(serr, ierr);
serr << "Expected (...)" << endl;
serr << "Found " << words[(int)words.size()-2].get_string() <<
words[(int)words.size()-1].get_string() << endl;
ierr = 2;
return;
}
// The starting index of the math expression.
int istart = i+1;
// Find the ending index of the math expression.
int iclose = find_closing_symbol("(", ")", istart);
if (iclose == -1) {
words[i+1].fatal_error(serr, ierr);
serr << "Did not find a closing parenthesis, ), in"
" math expression" << endl;
serr << "Check for unbalanced parentheses in math expression." << endl;
ierr = 2;
return;
}
int iend = iclose - 1;
int iwres = 0;
int nargs = 0;
for (;;) {
if (!handle_innermost_parens(istart, iend, iwres, nargs, true,
serr, ierr)) break;
bool isvar = false;
bool doit = true;
if (iwres <= 0) doit = false;
if (cmd_type == "assignment" && iwres==1) doit = false;
if (doit) {
isvar = evaluate_variable(iwres-1, iend, nargs, serr, ierr);
}
if ((iwres > 0) && (!isvar)) {
evaluate_function(iwres-1, iend, nargs, serr, ierr);
}
}
// Now set ihip1,2 to the original set of parens and handle those.
// This will do a math eval inside the parens, handle multiple arguments
// and possibly remove the parens.
// We do not remove the parens if this is a command line or an assignment
// line and we are to the left of the equals because a lot of checks
// depend on the parens being there.
int ihip1 = istart-1;
int ihip2 = iend + 1;
bool remp = true;
if (ieq>ihip2 && ihip1==1) remp = false;
handle_innermost_parens(ihip1, ihip2, iwres, nargs, remp,
serr, ierr);
// Handle the case of a variable array, i.e. evaluate and replace
// the variable array reference. We of course do not do this for
// an assignment statement where we are to the left of the equals.
//bool isvar = false;
bool doit = true;
if (iwres <= 0) doit = false;
if (cmd_type == "assignment" && iwres==1) doit = false;
if (doit) {
// We use ihip3 to account for ++ or -- following a
// variable array reference.
int ihip3 = ihip2 + 1;
//isvar = evaluate_variable(iwres-1, ihip3, nargs, serr, ierr);
evaluate_variable(iwres-1, ihip3, nargs, serr, ierr);
}
// Do not allow a function outside of parens. We could do this
// but for now all math is inside parens.
//if ((iwres > 0) && (!isvar)) {
// evaluate_function(iwres-1, iend, nargs, serr, ierr);
//}
} // if find (
} // End of loop through all words on the line.
//cout << "&&&&&cw Cmd.cc, Exit math_eval" << endl;
}
// ===========================================================================
// Handle the innermost set of parentheses.
// The return value is false if parens were not found or if they were
// unbalanced. If parens were found and handled then true is returned.
// iwres is an output quantity and is the location of the resultant word.
// ===========================================================================
bool Cmd::handle_innermost_parens(int &i1, int &i2, int &iwres, int &nargs,
bool remp, stringstream &serr, int &ierr)
{
// If no innermost parens are found then iwres is meaningless.
iwres = -1;
// Search for the innermost left parens. It is ok if there is no left
// parens, this line just does not have parens.
int ip1 = find_last("(", i1, i2);
if (ip1 == -1) return false;
// After evaluation, the resultant word will be at ip1.
iwres = ip1;
int ipstart = ip1 + 1;
bool done = false;
nargs = 1;
for (;;) {
int ip2 = find_any_char(ipstart, i2, ",)");
if (ip2 == -1) {
words[ipstart].fatal_error(serr, ierr);
serr << "Did not find a closing parenthesis, ), in"
" math expression" << endl;
serr << "Check for unbalanced parentheses in math expression." << endl;
ierr = 2;
return false;
}
if (words[ip2].get_string() == ")") done = true;
if (words[ip2].is_comma()) nargs += 1;
if (remp) {
delete_words(ip2, ip2);
i2 -= 1;
}
int ip21a = ip2 - 1;
int ip21 = ip2 - 1;
seval(ipstart, ip21, serr, ierr);
i2 -= ip21a - ip21;
if (done) break;
ipstart += 1;
}
// Delete the leading paren.
if (remp) {
delete_words(ip1, ip1);
i2 -= 1;
}
return true;
}
// ===========================================================================
// Simple evaluation of a series of words i1 to i2 inclusive.
// ===========================================================================
void Cmd::seval(int &i1, int &i2, stringstream &serr, int &ierr)
{
Parser_math pmath;
subvar_w0(i1, i2, serr, ierr);
handle_unary_op(i1, i2, "-", serr, ierr);
handle_unary_op(i1, i2, "+", serr, ierr);
// Level Operators
// ----- -----------------------
// 8 ()
// 7 ++ --
// 6 **
// 5 * /
// 4 + -
// 3 .gt. .ge. .lt. .le. .eq. .ne.
// 2 .not.
// 1 .and.
// 0 .or.
for (int level=6; level>=0; level--) {
for (int i=i1; i<=i2; i+=1) {
if (words[i].is_operator(level)) {
int ln = words[i].get_line_number();
int file_ln = words[i].get_file_line_number();
string fname = words[i].get_filename();
Word w("", ln, file_ln, fname, lines);
string op_type = words[i].get_op_type();
if (op_type == "arithmetic")
pmath.do_op(i-1, i, i+1, words, w, serr, ierr);
if (op_type == "relational")
pmath.do_op_relational(i-1, i, i+1, words, w, serr, ierr);
if (op_type == "logical" && level == 2) // .not. is unary
pmath.do_op_not(i, i+1, words, w, serr, ierr);
if (op_type == "logical" && level != 2)
pmath.do_op_logical(i-1, i, i+1, words, w, serr, ierr);
// level 2, .not., is unary and is handled differently.
if (level == 2) {
replace_words(i, i+1, w);
i2 -= 1;
}
else {
replace_words(i-1, i+1, w);
i2 -= 2;
i -= 1;
}
continue;
}
}
}
//cout << "&&&&&cw Leave seval" << endl;
}
// ===========================================================================
// Handle unary plus and minus.
// utype is either "+" or "-".
// ===========================================================================
void Cmd::handle_unary_op(int i1, int &ipend, string utype,
stringstream &serr, int &ierr)
{
int ipstart = i1;
for (;;) {
int ip = find(ipstart, ipend, utype);
// If we do not find any more plus/minus signs then we are done.
if (ip == -1) return;
// Fatal error is the plus/minus sign is the last word on the line.
if (ip >= ((int)words.size()-1)) {
words[ip].fatal_error(serr, ierr);
serr << "Found a " << utype << " sign at the end of a line." << endl;
serr << "Expected something to the right of the " << utype
<< " sign to operate on." << endl;
ierr = 2;
return;
}
// If the plus/minus sign is the first word on the line, then it has
// to be a unary op. The word after the plus/minus sign must be a
// number or variable.
// This should never happen because we are always inside (...)
// and will never be word 0, still we should be general and take care
// of this case.
if (ip == 0) {
if (!words[ip+1].is_number()) {
words[ip+1].fatal_error(serr, ierr);
serr << "Expected the object following the unary " << utype
<< " to be a number." << endl;
serr << "Instead, it was " << words[ip+1].get_string() << endl;
ierr = 2;
return;
}
// Actually do the negate operation.
do_unary_op(ip, utype);
ipstart = ip+1;
ipend -= 1;
continue;
}
// Check to see if the +/- is a binary op. If so, then nothing needs
// to be done with this +/- sign, binary ops are handled elsewhere.
if (words[ip-1].is_number() &&
(words[ip+1].is_number() || words[ip+1].get_string() == "-" ||
words[ip+1].get_string() == "+")
) {
ipstart = ip+1;
continue;
}
// Check to see if the +/- is a unary op.
if (!words[ip-1].is_number() && words[ip+1].is_number()) {
do_unary_op(ip, utype);
ipstart = ip+1;
ipend -= 1;
continue;
}
// Check for an error.
if (!words[ip-1].is_number() &&
!words[ip+1].is_number()) {
words[ip-1].fatal_error(serr, ierr);
serr << "Expected the object following the unary " << utype << " to"
" be a number." << endl;
serr << "Instead, it was " << words[ip+1].get_string() << endl;
ierr = 2;
return;
}
words[ip].fatal_error(serr, ierr);
serr << "Unknown error with unary " << utype << endl;
serr << "Error with words: " << endl;
serr << words[ip].get_string() << words[ip+1].get_string() << endl;
ierr = 2;
return;
}
}
// ===========================================================================
// Do a unary operation.
// The minus sign is at word ip and the word to be negated is at word ip+1.
// After negation, both words get replaced by the new negated word.
// If the unary op is plus then all we need to do is get rid of the + sign.
// ===========================================================================
void Cmd::do_unary_op(int ip, string utype)
{
if (utype == "+") {
delete_words(ip,ip);
return;
}
if (utype == "-") {
if (words[ip+1].is_number()) {
Word w = words[ip+1];
w.negate_value();
replace_words(ip, ip+1, w);
return;
}
}
}
// ===========================================================================
// Check to see that all ++ and -- have been handled and removed.
// ===========================================================================
void Cmd::check_ppmm(stringstream &serr, int &ierr)
{
for (int i=0; i<(int)words.size(); i++) {
string s = words[i].get_string();
if (s == "++" || s == "--") {
words[i].fatal_error(serr, ierr);
serr << "Misplaced " << s << " operator." << endl;
serr << "++ and -- operators must follow a variable or " << endl;
serr << "an element of an array variable. " << endl;
ierr = 2;
}
}
}
// ===========================================================================
// Evaluate a function.
// ===========================================================================
void Cmd::evaluate_function(int iw1, int &i2, int &nargs,
stringstream &serr, int &ierr)
{
// If there is no map of functions, then we do nothing.
if (fmap == NULL) return;
// If the word at iw1 is not a string then it will not be a function.
if (!words[iw1].is_string()) return;
// Find the function.
string s = words[iw1].get_string();
map<string, Function>::iterator p;
p = fmap->find(s);
// The function was not found.
if (p == fmap->end()) {
words[iw1].fatal_error(serr, ierr);
serr << "Expected a function" << endl;
serr << "Instead found: " << words[iw1].get_string() << endl;
serr << "The list of known functions is:" << endl;
for (p=fmap->begin(); p!=fmap->end(); p++) {
serr << p->second.get_name() << endl;
}
ierr = 2;
return;
}
// The function was found, do the evaluation and replace the words.
// Common items needed for all types of functions.
int ln = words[iw1].get_line_number();
int file_ln = words[iw1].get_file_line_number();
string fname = words[iw1].get_filename();
// Is a variable defined or not.
if (s == "defined") {
string varname = words[iw1+1].get_string();
string result = "true";
map<string, Variable>::iterator p;
p = vmap->find(varname);
if (p == vmap->end()) result = "false";
Word w(result, ln, file_ln, fname, lines);
replace_words(iw1, iw1+nargs, w);
i2 -= nargs;
return;
}
// String functions - string arguments, string results.
if (p->second.get_type() == "string") {
// Load all the arguments into a vector of strings.
vector<string> vs;
for (int i=0; i<nargs; i++) {
int j = iw1 + 1 + i;
string s = words[j].get_string();
vs.push_back(s);
}
// Calculate the function and replace the words.
string result = p->second.evaluate(vs, serr, ierr, ln, file_ln,
fname, lines);
Word w(result, ln, file_ln, fname, lines);
replace_words(iw1, iw1+nargs, w);
i2 -= nargs;
}
// Real functions - double arguments, double results.
if (p->second.get_type() == "real") {
// Check to see if all the function arguments have a value.
bool has_value = true;
for (int i=0; i<nargs; i++) {
int j = iw1 + 1 + i;
if (!words[j].is_number()) {
has_value = false;
words[j].fatal_error(serr, ierr);
serr << "Expected a number for function argument number " <<
i+1 << endl;
serr << "Instead found: " << words[j].get_string() << endl;
ierr = 2;
}
}
if (!has_value) return;
// All the arguments have values so we can deal with them
// as doubles.
// Load all the arguments into a vector of doubles.
vector<double> vd;
for (int i=0; i<nargs; i++) {
int j = iw1 + 1 + i;
double d = words[j].get_double();
vd.push_back(d);
}
// Calculate the function and replace the words.
double result = p->second.evaluate(vd, serr, ierr, ln, file_ln,
fname, lines);
Word w(result, ln, file_ln, fname, lines);
replace_words(iw1, iw1+nargs, w);
i2 -= nargs;
}
}
// ===========================================================================
// When two "*" characters are together, assume that is the exponentiation
// operator, "**", and replace both "*"'s with "**".
// ===========================================================================
void Cmd::handle_star_star()
{
for (int i=0; i<(int)words.size()-1; i++) {
if (words[i].get_string() == "*" && words[i+1].get_string() == "*") {
int lnum = words[i].get_line_number();
int file_ln = words[i].get_file_line_number();
string fname = words[i].get_filename();
string s = "**";
Word w(s, lnum, file_ln, fname, lines);
replace_words(i, i+1, w);
}
}
}
// ===========================================================================
// The parser does not automatically separate operators like .eq., .ne., etc.
// For example, the phrase a.eq.b will be one word when it should be 3 words.
// This routine finds those cases and splits the one word into multiple words.
// ===========================================================================
void Cmd::handle_ops()
{
vector<string> subs;
subs.push_back(".eq.");
subs.push_back(".ne.");
subs.push_back(".gt.");
subs.push_back(".ge.");
subs.push_back(".lt.");
subs.push_back(".le.");
subs.push_back(".hgeq.");
subs.push_back(".hgne.");
subs.push_back(".hggt.");
subs.push_back(".hgge.");
subs.push_back(".hglt.");
subs.push_back(".hgle.");
subs.push_back(".not.");
subs.push_back(".and.");
subs.push_back(".or.");
for (int i=0; i<(int)words.size(); i++) {
string fstr = words[i].get_string();
for (int j=0; j<(int)subs.size(); j++) {
vector<string> vs;
bool b = separate_str(subs[j], fstr, vs);
if (b) {
vector<Word> vw;
for (int k=0; k<(int)vs.size(); k++) {
int lnum = words[i].get_line_number();
int file_lnum = words[i].get_file_line_number();
string fname = words[i].get_filename();
Word w(vs[k], lnum, file_lnum, fname, lines);
vw.push_back(w);
//cout << vs[k] << endl;
}
replace_words(i, i, vw);
i--;
break;
}
}
//int lnum = words[i].get_line_number();
//string s = "**";
//Word w(s, lnum, lines);
//replace_words(i, i+1, w);
}
}
// ===========================================================================
// After the line has mostly been processed, check for any misplaced math
// operations. For example, the following line
// xcenter = 1.0 + 2.0
// has a misplaced math op in it, i.e. it should be in parentheses
// xcenter = (1.0 + 2.0)
// ===========================================================================
void Cmd::check_misplaced_math(stringstream &serr, int &ierr)
{
for (int i=0; i<(int)words.size(); i++) {
if (words[i].is_operator()) {
words[i].fatal_error(serr, ierr);
serr << "Misplaced math operation." << endl;
serr << "All math operations must be inside parentheses." << endl;
ierr = 2;
}
}
}
// ***************************************************************************
// ***************************************************************************
// Handle if/elseif/else/endif
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Handle if/elseif/else/endif statements.
// ===========================================================================
void Cmd::handle_if(bool &skip, deque<bool> &skip_level,
deque<bool> &satisfied, stringstream &serr, int &ierr)
{
// If's can be nested to any level, the number of levels is determined
// by the size of skip_level, the size of satisfied would also work here.
int nlevels = (int)skip_level.size();
// The endif statement ends a block if.
if (words[0].get_string() == "endif") {
if ((int)words.size() > 1) {
words[1].fatal_error(serr, ierr);
serr << "The endif (or end if) statement should not have "
"anything else on the line." << endl;
serr << "Found other words on the line." << endl;
ierr = 2;
}
// The if level has ended, just erase it.
skip_level.erase(skip_level.begin()+nlevels-1);
satisfied.erase(satisfied.begin()+nlevels-1);
skip = true;
return;
}
// Else statment.
if (words[0].get_string() == "else") {
if ((int)words.size() > 1) {
words[1].fatal_error(serr, ierr);
serr << "The else statement should not have "
"anything else on the line." << endl;
serr << "Found other words on the line." << endl;
ierr = 2;
}
// If the if has been satisfied before this else, then just
// skip the else block. Otherwise the if will be satisfied and
// we do not skip the else block.
if (satisfied[nlevels-1]) {
skip_level[nlevels-1] = true;
}
else {
satisfied[nlevels-1] = true;
skip_level[nlevels-1] = false;
}
skip = true;
return;
}
// If any level is in skip mode, then we will skip this line.
// This is mostly for non if related lines, but the skip flag is
// used below.
skip = false;
for (int n=0; n<nlevels; n++) {
if (skip_level[n]) {
skip = true;
}
}
// Process the elseif statement.
if (words[0].get_string() == "elseif") {
if (satisfied[nlevels-1]) {
skip_level[nlevels-1] = true;
}
else {
// If we are in skip mode at a higher level, then we can ignore this
// elseif.
if (skip && !skip_level[nlevels-1]) return;
// Do some syntax checking.
int wsize = (int)words.size();
if (wsize > 1) {
if (words[1].get_string() != "(") {
words[1].fatal_error(serr, ierr);
serr << "Expected an open parentheses, (, following " <<
words[0].get_string() << endl;
serr << "Instead found: " << words[1].get_string() << endl;
ierr = 2;
}
}
int nw = wsize-2;
if (nw >= 0) {
if (words[nw].get_string() != ")") {
words[nw].fatal_error(serr, ierr);
serr << "Expected a close parentheses, ), as the next to last "
"symbol on the line." << endl;
serr << "Instead found: " << words[nw].get_string() << endl;
ierr = 2;
}
}
nw = wsize-1;
if (nw >= 0) {
if (words[nw].get_string() != "then") {
words[nw].fatal_error(serr, ierr);
serr << "Expected then as the last word on the line." << endl;
serr << "Instead found: " << words[nw].get_string() << endl;
ierr = 2;
}
}
// Evaluate the conditional.
math_eval(serr, ierr);
if (words[1].get_bool(serr, ierr)) {
satisfied[nlevels-1] = true;
skip_level[nlevels-1] = false;
}
else {
skip_level[nlevels-1] = true;
}
}
// Set skip to skip the elseif statement.
skip = true;
return;
}
if (words[0].get_string() == "if") {
//cout << "&&&&&cw Cmd.cc, if statment encountered" << endl;
// If we are in skip mode at a higher level, then we can ignore this
// if.
if (skip) {
skip_level.push_back(true);
satisfied.push_back(true);
return;
}
// Do some syntax checking.
int wsize = (int)words.size();
if (wsize > 1) {
if (words[1].get_string() != "(") {
words[1].fatal_error(serr, ierr);
serr << "Expected an open parentheses, (, following " <<
words[0].get_string() << endl;
serr << "Instead found: " << words[1].get_string() << endl;
ierr = 2;
}
}
// Evaluate the conditional.
math_eval(serr, ierr);
//for (int i=0; i<(int)words.size(); i++) {
// cout << words[i].get_string() << endl;
//}
// Single line if
if (words[2].get_string() != "then") {
if (words[1].get_bool(serr, ierr)) {
delete_words(0,1);
reset_name_type();
skip = false;
}
else {
skip = true;
}
return;
}
// Multi-block if
if (words[2].get_string() == "then") {
if (words[1].get_bool(serr, ierr)) {
skip_level.push_back(false);
satisfied.push_back(true);
skip = true;
}
else {
skip_level.push_back(true);
satisfied.push_back(false);
skip = true;
}
return;
}
}
//for (int i=0; i<(int)words.size(); i++) {
// if (words[i].get_string() == "*" && words[i+1].get_string() == "*") {
// }
//}
}
// ***************************************************************************
// ***************************************************************************
// Handle do loops
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Handle do loops.
// ===========================================================================
void Cmd::handle_do(bool &skip, deque<int> &do_start, int &cdex,
bool &end_do_loop, stringstream &serr, int &ierr)
{
// Do's can be nested to any level, the number of levels is determined
// by the size of do_start.
int nlevels = (int)do_start.size();
// End of do loop, go back to do line.
if (words[0].get_string() == "enddo") {
//cout << "&&&&&cw Cmd, handle_do, start of enddo, cdex=" << cdex << endl;
if ((int)words.size() > 1) {
words[1].fatal_error(serr, ierr);
serr << "The enddo (or end do) statement should not have "
"anything else on the line." << endl;
serr << "Found other words on the line." << endl;
ierr = 2;
}
cdex = do_start[nlevels-1] - 1;
skip = true;
return;
}
// Cycle command encountered.
if (words[0].get_string() == "cycle") {
if ((int)words.size() > 1) {
words[1].fatal_error(serr, ierr);
serr << "The cycle statement should not have "
"anything else on the line." << endl;
serr << "Found other words on the line." << endl;
ierr = 2;
}
cdex = do_start[nlevels-1] - 1;
skip = true;
return;
}
// Break out of the do loop.
if (words[0].get_string() == "exit") {
if ((int)words.size() > 1) {
words[1].fatal_error(serr, ierr);
serr << "The exit statement should not have "
"anything else on the line." << endl;
serr << "Found other words on the line." << endl;
ierr = 2;
}
end_do_loop = true;
return;
}
if (words[0].get_string() == "do") {
//for (int i=0; i<(int)words.size(); i++) {
// cout << words[i].get_string() << endl;
//}
// Evaluate any math expressions on the do line.
math_eval(serr, ierr);
// Replace any simple variables on the line with their values.
// Of course, do not replace the loop variable.
int ieq = -1;
for (int i=0; i<(int)words.size(); i++) {
if (words[i].get_string() == "=") {
ieq = i;
break;
}
}
if (ieq >= 0) {
int nw1 = (int)words.size()-1;
subvar_w0(ieq+1, nw1, serr, ierr);
}
// Handle unary minus
handle_cmd_unary_minus(serr, ierr);
// Number of words on the line after math evaluation.
int nwords = (int)words.size();
// Get the loop variable name.
string do_varname = "$i";
bool isvar = true;
if (nwords>1) {
do_varname = words[1].get_string();
if (!words[1].is_variable()) isvar = false;
}
// Expecting 6 or 8 words, i.e. "do $i = 1 , 10"
if (nwords < 6) {
words[0].fatal_error(serr, ierr);
serr << "Expected at least 6 words on this line after any math evaluations."
<< endl;
serr << "For example, " << endl
<< " do " << do_varname << " = 1 , 10" << endl;
serr << "Instead found " << nwords << " words on the line." << endl;
serr << "The line (after any math evaluations have been done) is:" << endl;
serr << " ";
for (int iw=0; iw<nwords; iw++) {
serr << words[iw].get_string() << " ";
}
serr << endl;
ierr = 2;
return;
}
// Variables must begin with a $
if (!isvar) {
words[0].fatal_error(serr, ierr);
serr << "Loop variable names must begin with a $ sign." << endl;
serr << "Note that putting quotes around a variable name makes it" << endl;
serr << "a string, not a variable." << endl;
serr << "Instead found: " << do_varname << endl;
ierr = 2;
return;
}
// Word 3 must be an = sign.
if (words[2].get_string() != "=") {
words[2].fatal_error(serr, ierr);
serr << "The third word must be an = sign." << endl;
serr << "Instead found: " << words[2].get_string() << endl;
ierr = 2;
return;
}
// Word 5 must be a comma.
if (!words[4].is_comma()) {
words[4].fatal_error(serr, ierr);
serr << "The fifth word must be a comma." << endl;
serr << "Instead found: " << words[4].get_string() << endl;
ierr = 2;
return;
}
// The get_int functions generate an error if the values are
// not integer.
int i1 = words[3].get_int(serr, ierr);
int i2 = words[5].get_int(serr, ierr);
// Get the step (increment) if specified.
int istep = 1;
if (nwords > 7) istep = words[7].get_int(serr, ierr);
string s1 = words[3].get_string();
string s2 = words[5].get_string();
bool do_continue = false;
if (nlevels > 0) {
if (do_start[nlevels-1] == cdex) do_continue = true;
}
if (do_continue) { // This do has already been encountered.
//cout << "&&&&&cw Cmd, handle_do, do:continue" << endl;
// Find the variable in the list of variables, increment it, test for
// ending the loop, and store the incremented value.
map<string, Variable>::iterator p;
p = vmap->find(do_varname);
if (p != vmap->end()) {
string do_var_value = p->second.get_var_value();
int lnum = words[0].get_line_number();
int file_lnum = words[0].get_file_line_number();
string fname = words[0].get_filename();
Word w(do_var_value, lnum, file_lnum, filename, lines);
int ival = w.get_int(serr, ierr);
ival += istep;
//cout << "&&&&&cw Cmd, handle_do, do:continue, do_var_value=" << ival << endl;
if (istep >= 0 && ival > i2) {
end_do_loop = true;
return;
}
if (istep < 0 && ival < i2) {
end_do_loop = true;
return;
}
stringstream ss;
ss << ival;
string sval = ss.str();
vector<string> valvec;
valvec.push_back(sval);
vector<int> istart(0,0);
p->second.set_var_value(istart, valvec, lnum, file_lnum, fname,
lines, serr, ierr);
}
else {
words[1].fatal_error(serr, ierr);
serr << "The loop variable, " << do_varname <<
" was not found in the variable list." << endl;
serr << "This should not happen, possible code bug?" << endl;
ierr = 2;
return;
}
}
else { // A new do loop has been encountered.
do_start.push_back(cdex);
// It is possible that we don't execute the do loop at all.
if (istep >= 0 && i1 > i2) {
end_do_loop = true;
return;
}
if (istep < 0 && i1 < i2) {
end_do_loop = true;
return;
}
//cout << "&&&&&cw Cmd, handle_do, do:start, cdex=" << cdex << endl;
//cout << "&&&&&cw Cmd, handle_do, do:start, s1=" << s1 << endl;
//cout << "&&&&&cw Cmd, handle_do, do:start, s2=" << s2 << endl;
// Store the loop variable, create it if necessary.
vector<int> istart(0,0);
int lnum = words[0].get_line_number();
int file_lnum = words[0].get_file_line_number();
string fname = words[0].get_filename();
vector<string> valvec;
valvec.push_back(s1);
map<string, Variable>::iterator p;
p = vmap->find(do_varname);
if (p != vmap->end()) {
p->second.set_var_value(istart, valvec, lnum, file_lnum, fname,
lines, serr, ierr);
}
else {
Variable v(do_varname, istart, valvec, lnum, file_lnum, fname,
lines, serr, ierr);
vmap->insert(pair<string, Variable>(v.get_varname(), v));
}
}
skip = true;
return;
}
}
// ===========================================================================
// Starting at a do statement, find the matching enddo.
// ===========================================================================
bool Cmd::find_matching_enddo(int &dlev, bool &stop_checking)
{
if (words[0].get_string() == "enddo") {
if (dlev == 1) return true;
dlev -= 1;
return false;
}
if (words[0].get_string() == "do") {
dlev += 1;
return false;
}
// If we are in main and hit a subroutine statement then that is the
// end of main and we need to stop checking.
if (words[0].get_string() == "subroutine") {
stop_checking = true;
return false;
}
// If we are in a subroutine and hit an endsubroutine statement then
// we need to stop checking.
if (words[0].get_string() == "endsubroutine") {
stop_checking = true;
return false;
}
return false;
}
// ***************************************************************************
// ***************************************************************************
// Subroutines
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Handle subroutines.
// ===========================================================================
void Cmd::handle_subroutines(bool &skip, bool &go_to_sub, string &sub_name,
bool &go_to_call, stringstream &serr, int &ierr)
{
// To suppress compiler warnings of unused parameters
assert(skip == skip);
//assert(serr == serr);
assert(ierr == ierr);
//
if (words[0].get_string() == "call") {
sub_name = words[1].get_string();
go_to_sub = true;
return;
}
if (words[0].get_string() == "endsubroutine" ||
words[0].get_string() == "return") {
go_to_call = true;
return;
}
}
// ===========================================================================
// Searching for subroutine sub_name.
// ===========================================================================
bool Cmd::find_subroutine(string &sub_name)
{
if ((int)words.size() < 2) return false;
if (words[0].get_string() == "subroutine" &&
words[1].get_string() == sub_name) return true;
return false;
}
// ===========================================================================
// A call statement has been encountered, get the arguments, if any.
// The call is expected to be
// call subname ( arg1, arg2, ...)
// ===========================================================================
void Cmd::get_call_args(vector<string> &sargs, vector<bool> &sargs_isvar,
stringstream &serr, int &ierr)
{
//debug_print_words("Cmd, enter get_call_args");
// We do not want to modify the words on this line, but we have to
// temporarily to get the math eval to work right. Therefore store the
// words on the line and restore them later.
deque <Word> words_store;
for (int i=0; i<(int)words.size(); i++) {
words_store.push_back(words[i]);
}
// Erase the call and the subroutine name.
erase_word(0);
erase_word(0);
// Erase the opening and closing parens.
if ((int)words.size() > 0) {
if (words[0].get_string() == "(") erase_word(0);
}
if ((int)words.size() > 0) {
if (words[(int)words.size()-1].get_string() == ")") erase_last_word();
}
//debug_print_words("Cmd, get_call_args, after erase");
// Do a math eval to get one word arguments. If the arguments are
// variables they will not be evaluated, so we will end up with a mix
// of variables and numbers.
if ((int)words.size() > 0) {
math_eval(serr, ierr);
int wlen = (int)words.size() - 1;
handle_unary_op(0, wlen, "-", serr, ierr);
wlen = (int)words.size() - 1;
handle_unary_op(0, wlen, "+", serr, ierr);
}
//debug_print_words("Cmd, get_call_args, after math eval");
// Store the arguments in the vector of strings, sargs, to be returned
// to the calling code and also store in the class, call_args.
call_args.clear();
call_args_isvar.clear();
for (int i=0; i<(int)words.size(); i++) {
if (words[i].is_comma()) continue;
sargs.push_back(words[i].get_string());
sargs_isvar.push_back(words[i].is_variable());
call_args.push_back(words[i].get_string());
call_args_isvar.push_back(words[i].is_variable());
}
// Restore the words before leaving this function.
words.clear();
for (int i=0; i<(int)words_store.size(); i++) {
words.push_back(words_store[i]);
}
//debug_print_words("Cmd, get_call_args, after restoring words");
}
// ===========================================================================
// A subroutine statement has been encountered, get the arguments, if any.
// The subroutine statement is expected to be
// subroutine subname ( arg1, arg2, ...)
// ===========================================================================
void Cmd::get_sub_args(vector<string> &sargs, vector<bool> &sargs_isvar)
{
sub_args.clear();
sub_args_isvar.clear();
for (int i=3; i<(int)words.size(); i+=2) {
sargs.push_back(words[i].get_string());
sargs_isvar.push_back(words[i].is_variable());
sub_args.push_back(words[i].get_string());
sub_args_isvar.push_back(words[i].is_variable());
}
}
// ===========================================================================
// Accessor functions for the calling and subroutine arguments.
// ===========================================================================
void Cmd::copy_call_args(vector<string> &sargs, vector<bool> &sargs_isvar)
{
for (int i=0; i<(int)call_args.size(); i++) {
sargs.push_back(call_args[i]);
sargs_isvar.push_back(call_args_isvar[i]);
}
}
void Cmd::copy_sub_args(vector<string> &sargs, vector<bool> &sargs_isvar)
{
for (int i=0; i<(int)sub_args.size(); i++) {
sargs.push_back(sub_args[i]);
sargs_isvar.push_back(sub_args_isvar[i]);
}
}
// ***************************************************************************
// ***************************************************************************
// Handle comments.
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Process single line comments.
// ===========================================================================
void Cmd::single_line_comments()
{
for (int i=0; i<(int)words.size()-1; i++) {
if ((words[i].get_string() == "!") ||
(words[i].get_string() == "#") ||
(words[i].get_string() == "/" && words[i+1].get_string() == "/")
) {
words.erase(words.begin()+i, words.begin()+(int)words.size());
break;
}
}
// Handle the case where the last word might be an ! or a #
int ilast = (int)words.size()-1;
if (ilast < 0) return;
if ((words[ilast].get_string() == "!") || (words[ilast].get_string() == "#"))
words.erase(words.begin()+ilast);
}
// ===========================================================================
// Process multi-line comments.
// ===========================================================================
void Cmd::multi_line_comments(int &level)
{
/*
cout << "*** Enter multi_line_comments, level=" << level << endl;
cout << "**** original string: " << original_str << endl;
stringstream ss;
print_using_words(ss);
cout << " print words before:" << endl;
cout << " " << ss.str() << endl;
*/
int istart = -1;
if (level > 0) istart = 0;
for (int i=0; i<(int)words.size()-1; i++) {
//cout << "Top of i loop, i=" << i << endl;
if (words[i].get_string() == "/" && words[i+1].get_string() == "*") {
if (level == 0) istart = i;
level += 1;
//cout << "found /*, i=" << i << endl;
i += 1;
continue;
}
if (words[i].get_string() == "*" && words[i+1].get_string() == "/") {
if (level == 0) {
cout << "Error in line " << line_number << " umatched */" << endl;
}
//cout << "found */, istart=" << istart << " words[istart]=" <<
// words[istart].get_string() << endl;
//cout << "found */, i=" << i << " words[i]=" <<
// words[i].get_string() << endl;
//cout << "found */, i+1=" << i+1 << " words[i+1]=" <<
// words[i+1].get_string() << endl;
words.erase(words.begin()+istart, words.begin()+i+2);
level -= 1;
int ndel = i+1-istart+1;
//cout << "ndel=" << ndel << " i=" << i << endl;
i = i+1-ndel+1;
//cout << "after changing i, i=" << i << " words[i]=" <<
// words[i].get_string() << endl;
if (level > 0) istart = i;
continue;
}
}
if (level > 0) {
words.erase(words.begin()+istart, words.begin()+(int)words.size());
}
/*
stringstream ss1;
print_using_words(ss1);
cout << " print words after:" << endl;
cout << " " << ss1.str() << endl;
cout << endl << "**********" << endl;
*/
}
// ***************************************************************************
// ***************************************************************************
// Miscellaneous
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Go through every word on the line, check for matching quotes, then remove
// them.
// ===========================================================================
void Cmd::handle_quotes(stringstream &serr, int &ierr)
{
for (int i=0; i<(int)words.size(); i++) {
words[i].handle_quotes(serr, ierr);
}
}
// ===========================================================================
// The execution line arguments are expected to be of the form
// -v r1=5 -v somevar = 14
// The -v is a keyword indicating that a variable setting follows. There is
// no $ because the shell does not allow that.
// At this point the execution line arguments have been parsed into words.
// This function extracts the variable definitions, inserts the $, and turns
// them into separate lines, and returns that in string sout. Then the
// parser can simply add that to the top of the user input file.
// ===========================================================================
void Cmd::handle_exe_args(string &sout)
{
// We use a stringstream here instead of modifying sout directly so that
// we can use endl instead of \n since endl is portable and \n is not.
stringstream ss;
bool line;
for (int i=0; i<(int)words.size()-1; i++) {
// if you hit a -<letter>
if (words[i].get_string() == "-" &&
( words[i+1].get_string() == "v" ||
words[i+1].get_string() == "l" ) ) {
// get type of argument
line = true;
if( words[i+1].get_string() == "v" ){
line = false;
}
// move in after the -<letter> and stuff line until next -<letter>
int istart = i+2;
for (int j=istart; j<(int)words.size(); j++) {
// stop at next -<letter>
if (j < (int)words.size()-1) {
if (words[j].get_string() == "-" &&
( words[j+1].get_string() == "v" ||
words[j+1].get_string() == "l" ) ) {
break;
}
}
string sj = words[j].get_string();
if (j == istart && !line) {
sj.insert(sj.begin(), '$');
}
ss << sj << " ";
}
ss << endl;
}
}
sout = ss.str();
}
// ===========================================================================
// Consider the following input
// 2.0, 3.0 e15, -7.0
// The issue is with the middle two words, "3.0 e15", the old parser ignored
// the space and treated this as one word, 3.0e15. The new parser treats is
// as two words.
//
// This should have been treated as an input error by the old parser but was
// not, so now we have to deal with it.
//
// This routine detects this situation and allows the calling code to deal
// with it according to the action input, allowed action values are:
//
// ignore Treat it as two words and silently continue.
// fix Merge the two words into one word, as the old parser did.
// error Generate a fatal error, force the user to fix it.
//
// ===========================================================================
void Cmd::deprecated_input01(string action, stringstream &serr, int &ierr)
{
//if (cmd_name != "depcmd01") return;
if (cmd_type != "command") return;
for (int i=0; i<(int)words.size()-2; i++) {
// A comma must be found first.
if (!words[i].is_comma()) continue;
// There could be a unary plus or minus on the next number, if so
// then skip it. At this point, the unary plus and minus have not
// been merged with their number.
int in1 = i+1;
string spm = words[i+1].get_string();
string s1;
if (spm == "+" || spm == "-") {
s1 = spm;
in1 += 1;
}
s1 += words[in1].get_string();
// in1 is where the first number is, it needs to be a number.
if (!words[in1].is_number()) continue;
// in2 is where the second number is, it needs to be a number.
int in2 = in1 + 1;
if (in2 > (int)words.size() - 1) break;
if (!words[in2].is_number()) continue;
string s2 = words[in2].get_string();
s2.erase(s2.begin());
// The first character of the second number should be an e or E.
// But at this point we have already detected this and prepended
// a 1 on to the word. So the first character should be 1 and the
// second character should be e or E
char c30 = words[in2].get_string()[0];
if (c30 != '1') continue;
char c31 = words[in2].get_string()[1];
if (c31 != 'e' && c31 != 'E') continue;
// The next word, if present should be a comma.
int ic2 = in2 + 1;
if (ic2 <= (int)words.size()-1) {
if (!words[ic2].is_comma()) continue;
}
// A deprecated input has been found, ignore it, fix it, or
// generate a fatal error.
if (action == "ignore") continue;
if (action == "fix") {
words[in2].erase_char(0);
merge_words(in1, in2);
continue;
}
if (action == "error") {
words[in1].fatal_error(serr, ierr);
serr << "Possible error, detected the following" << endl;
serr << " comma digits space exponent comma" << endl;
serr << "The digits and exponent are separated by one or more spaces,"
<< endl;
serr << "this is not allowed for a single number." << endl;
serr << "The digits are: " << s1 << " and the exponent is: "
<< s2 << endl;
serr << "If this is one number, then remove the space." << endl;
serr << "If this is two numbers, then put a comma between the"
<< " digits and exponent."<< endl;
serr << "This error (and the same error in subsequent lines) can"
<< endl;
serr << "be controlled with the following input file command"
<< endl;
serr << "and arguments (put before the lines with errors)" << endl;
serr << " depcmd_dse argument" << endl;
serr << "where argument has one of the following values:" << endl;
serr << " fix Silently remove the space, merge into one number" << endl;
serr << " ignore Silently treat as two numbers" << endl;
serr << " error Generate fatal error (default)" << endl;
serr << "While the default is to generate an error, if the" << endl;
serr << "command name is matdef, then the default is fix." << endl;
ierr = 2;
continue;
}
}
}
// ===========================================================================
// Fatal error
// This is mainly meant to be called from some other class that does not
// know about words.
// ===========================================================================
void Cmd::fatal_error(int iw, stringstream &serr, int &ierr)
{
// To suppress compiler warnings of unused parameters
assert(ierr == ierr);
int lnum = words[iw].get_line_number();
int file_lnum = words[iw].get_file_line_number();
string fname = words[iw].get_filename();
serr << endl;
serr << "*** FATAL ERROR in line " << file_lnum << ":" << endl;
serr << " " << (*lines)[lnum-1] << endl;
serr << "in file: " << fname << endl;
}
// ===========================================================================
// This is meant to be called from within this class.
// ===========================================================================
void Cmd::fatal_error2(stringstream &serr, int &ierr)
{
// To suppress compiler warnings of unused parameters
assert(ierr == ierr);
serr << endl;
serr << "*** FATAL ERROR in line " << file_line_number << ":" << endl;
serr << " " << (*lines)[line_number-1] << endl;
serr << "in file: " << filename << endl;
}
// ===========================================================================
// Warning
// ===========================================================================
void Cmd::warning(int iw, stringstream &serr, int &ierr)
{
// To suppress compiler warnings of unused parameters
assert(ierr == ierr);
int lnum = words[iw].get_line_number();
int file_lnum = words[iw].get_file_line_number();
string fname = words[iw].get_filename();
serr << endl;
serr << "*** WARNING in line " << file_lnum << ":" << endl;
serr << " " << (*lines)[lnum-1] << endl;
serr << "in file: " << fname << endl;
}
// ===========================================================================
// This is used when printing duplicate lines warnings.
// ===========================================================================
void Cmd::print_duplicate_line(int iw, stringstream &ss, int fn_width,
int lnum_width, string after_lnum)
{
int lnum = words[iw].get_line_number();
int file_lnum = words[iw].get_file_line_number();
string fname = words[iw].get_filename();
ss << setw(fn_width) << fname;
ss << setw(lnum_width) << file_lnum << after_lnum;
ss << (*lines)[lnum-1];
//print_using_words_fm(ss);
}
// ===========================================================================
// Get the filename size and line number size for formatting purposes
// when printing duplicate lines warnings.
// ===========================================================================
void Cmd::get_duplicate_sizes(int iw, int &fn_width, int &lnum_width)
{
int file_lnum = words[iw].get_file_line_number();
string fname = words[iw].get_filename();
fn_width = (int)fname.size();
lnum_width = 1;
if (file_lnum >= 10) lnum_width = 2;
if (file_lnum >= 100) lnum_width = 3;
if (file_lnum >= 1000) lnum_width = 4;
if (file_lnum >= 10000) lnum_width = 5;
if (file_lnum >= 100000) lnum_width = 6;
if (file_lnum >= 1000000) lnum_width = 7;
}
// ===========================================================================
// Look at the places where this function is called to understand the
// following indices.
// wdex = i Index into the words array.
// cdex = k C index in the output array.
// ===========================================================================
void Cmd::error_dup_line(string &cname, int wdex, int cdex,
vector<int> &dup_wdex1, vector<Cmd *> &dup_cmd1,
vector<int> &dup_vals, const vector<int> &size,
int dup_fatal, stringstream &serr, int &ierr)
{
if (dup_vals[cdex] == 0) {
dup_cmd1[cdex] = this;
dup_wdex1[cdex] = wdex;
}
dup_vals[cdex] += 1;
if (dup_fatal == 0) return;
if (dup_vals[cdex] > 1) {
int wdex1 = dup_wdex1[cdex];
Cmd *cmd = dup_cmd1[cdex];
// Get the dimension of the array, 0,1,2,3,...
int dim = (int)size.size();
if (dup_fatal == 2) words[wdex].fatal_error(serr, ierr);
if (dup_fatal == 1) words[wdex].warning(serr, ierr);
int tot_size = 1;
for (int ts=0; ts<dim; ts++) {
tot_size *= size[ts];
}
vector<int> irdices(dim, 0);
Parser_utils putils(index_base);
putils.reverse_dex(cdex, tot_size, irdices, size);
serr << "A duplicate value has been specified for: " << cname << "(";
for (int irdex=0; irdex<dim; irdex++) {
serr << irdices[irdex];
if (irdex < dim-1) serr << ",";
}
serr << ") = " <<
words[wdex].get_string() << endl;
serr << "This array element was first specified in line " <<
cmd->get_file_line_number(wdex1) << endl;
string fname = cmd->get_filename(wdex1);
serr << " " << (*lines)[cmd->get_line_number(wdex1)-1] <<
endl;
serr << "in file: " << fname << endl;
if (dup_fatal == 2) {
serr << "This fatal error can be turned into a warning with the command " <<
endl << " duplicate_array_values = warning" << endl;
}
if (dup_fatal == 1) {
serr << "This warning can be turned into a fatal error with the command " <<
endl << " duplicate_array_values = fatal" << endl;
}
serr << "Duplicate array value checking can be turned off totally with" <<
endl << " duplicate_array_values = none" << endl;
serr << "This is not recommended since you will lose the opportunity" <<
endl << "to check for legimate errors in your input." << endl;
ierr = 1;
if (dup_fatal == 2) ierr = 3;
}
}
// ===========================================================================
// There are some commands that can be written as two words such as "end if",
// "else if", and "end do". Find these and combine them into one word.
// ===========================================================================
void Cmd::handle_two_words()
{
// Handle + + -> ++
for (int i=0; i<(int)words.size()-1; i++) {
if (words[i].get_string() == "+" && words[i+1].get_string() == "+") {
bool combine = false;
if (i == (int)words.size()-2) combine = true;
if (i < (int)words.size()-2) {
if (!words[i+2].is_numvar()) combine = true;
}
if (combine) {
int lnum = words[i].get_line_number();
int file_lnum = words[i].get_file_line_number();
string fname = words[i].get_filename();
string s = "++";
Word w(s, lnum, file_lnum, fname, lines);
replace_words(i, i+1, w);
}
}
}
// Handle - - -> --
for (int i=0; i<(int)words.size()-1; i++) {
if (words[i].get_string() == "-" && words[i+1].get_string() == "-") {
bool combine = false;
if (i == (int)words.size()-2) combine = true;
if (i < (int)words.size()-2) {
if (!words[i+2].is_numvar()) combine = true;
}
if (combine) {
int lnum = words[i].get_line_number();
int file_lnum = words[i].get_file_line_number();
string fname = words[i].get_filename();
string s = "--";
Word w(s, lnum, file_lnum, fname, lines);
replace_words(i, i+1, w);
}
}
}
// The rest of these have at least two words on the line,
// like "end subroutine".
if ((int)words.size() < 2) return;
// Common settings.
int lnum = words[0].get_line_number();
int file_lnum = words[0].get_file_line_number();
string fname = words[0].get_filename();
// Handle the case where enddo is written as two
// words, just combine them into one word.
if (words[0].get_string() == "end" && words[1].get_string() == "do") {
string s = "enddo";
Word w(s, lnum, file_lnum, fname, lines);
replace_words(0, 1, w);
}
// Handle the case where endsubroutine is written as two
// words, just combine them into one word.
if (words[0].get_string() == "end" && words[1].get_string() == "subroutine") {
string s = "endsubroutine";
Word w(s, lnum, file_lnum, fname, lines);
replace_words(0, 1, w);
}
// Handle the case where endwhen is written as two
// words, just combine them into one word.
if (words[0].get_string() == "end" && words[1].get_string() == "when") {
string s = "endwhen";
Word w(s, lnum, file_lnum, fname, lines);
replace_words(0, 1, w);
}
// Handle the case where endif and elseif are written as two
// words, just combine them into one word.
if (words[0].get_string() == "end" && words[1].get_string() == "if") {
string s = "endif";
Word w(s, lnum, file_lnum, fname, lines);
replace_words(0, 1, w);
}
if (words[0].get_string() == "else" && words[1].get_string() == "if") {
string s = "elseif";
Word w(s, lnum, file_lnum, fname, lines);
replace_words(0, 1, w);
}
}
// ===========================================================================
// Check for end of input. There are several ways user input ends:
// End of file
// Encounter a stop command
// Encounter a fatal error command.
// ===========================================================================
bool Cmd::check_input_end(bool kill_run, stringstream &serr, int &ierr)
{
// To suppress compiler warnings of unused parameters
assert(kill_run == kill_run);
if (words[0].get_string() == "fatal_error") {
int lnum = words[0].get_line_number();
int file_lnum = words[0].get_file_line_number();
string fname = words[0].get_filename();
serr << endl;
serr << "*** User has issued a fatal_error command in line "
<< file_lnum << ":" << endl;
serr << " " << (*lines)[lnum-1] << endl;
serr << "in file: " << fname << endl;
serr << endl << "The user supplied fatal_error message is: " << endl;
serr << " ";
string s = (*lines)[lnum-1];
int i1 = s.find("f", 0);
for (int i=i1+12; i<(int)s.size(); i++) {
serr << s[i];
}
serr << endl;
ierr = 2;
return true;
}
if (words[0].get_string() == "stop") return true;
return false;
}
// ***************************************************************************
// ***************************************************************************
// Operations on the deque of words.
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Find the first occurrence of string s in part of the line.
// ===========================================================================
int Cmd::find(int i1, int i2, string s)
{
for (int i=i1; i<=i2; i++) {
if (words[i].get_string() == s) {
return i;
}
}
return -1;
}
// ===========================================================================
// Find the first occurrence of any character in string s in part of
// the line.
// ===========================================================================
int Cmd::find_any_char(int i1, int i2, string s)
{
for (int i=i1; i<=i2; i++) {
for (int j=0; j<(int)s.size(); j++) {
string ssub = s.substr(j, 1);
if (words[i].get_string() == ssub) {
return i;
}
}
}
return -1;
}
// ===========================================================================
// Find the last occurrence of string s in the line.
// ===========================================================================
int Cmd::find_last(string s, int i1, int i2)
{
int iloc = -1;
for (int i=i1; i<=i2; i++) {
if (words[i].get_string() == s) {
iloc = i;
}
}
return iloc;
}
// ===========================================================================
// Given an input string, fstr, and a sub string, subs, find the first
// occurrence of subs in fstr. Return in the string vector, vs, the string
// to the left of subs, if any, the sub string itself, subs, and the string
// to the right of subs, if any.
// ===========================================================================
bool Cmd::separate_str(string &subs, string &fstr, vector<string> &vs)
{
// Make sure the return vector is empty.
vs.clear();
// Find the sub string, if not found, then nothing more to do.
int loc = (int)fstr.find(subs,0);
if (loc == (int)string::npos) return false;
// If fstr only contains subs and nothing more, then there is nothing
// to do.
if (subs == fstr) return false;
// Anything to the left of the sub string is the first string returned.
if (loc > 0) {
string s1 = fstr.substr(0,loc);
vs.push_back(s1);
}
// The sub string itself is the second string returned.
vs.push_back(subs);
// Find the number of characters to the right of the sub string.
int subs_len = (int)subs.size();
int fstr_len = (int)fstr.size();
int istart = loc + subs_len;
int nchar = fstr_len - istart;
// Anything to the right of the sub string is the third string returned.
if (nchar > 0) {
string s2 = fstr.substr(istart,nchar);
vs.push_back(s2);
}
return true;
}
// ===========================================================================
// Find the location of the closing symbol that matches the opening symbol.
// Symbol examples are (), [], {}
// We assume that the opening symbol has been found and we are starting the
// search after the opening symbol location.
// Nesting is handled, for example, (...(...(...)...)...)
// ===========================================================================
int Cmd::find_closing_symbol(string opensym, string closesym, int i1)
{
int level = 0;
for (int i=i1; i<(int)words.size(); i++) {
string w = words[i].get_string();
if (w == opensym) {
level += 1;
continue;
}
if (w == closesym) {
if (level == 0) return i;
level -= 1;
continue;
}
}
return -1;
}
// ===========================================================================
// Delete words i1 through i2 inclusive from the deque.
// ===========================================================================
void Cmd::delete_words(int i1, int i2)
{
deque<Word>::iterator p = words.begin();
words.erase(p + i1, p + i2 + 1);
}
// ===========================================================================
// Replace words i1 through i2 inclusive with word w.
// ===========================================================================
void Cmd::replace_words(int i1, int i2, Word &w)
{
delete_words(i1, i2);
deque<Word>::iterator p = words.begin();
words.insert(p + i1, w);
}
// ===========================================================================
// Replace words i1 through i2 inclusive with all the words in vector vw.
// ===========================================================================
void Cmd::replace_words(int i1, int i2, vector<Word> &vw)
{
delete_words(i1, i2);
for (int i=(int)vw.size()-1; i>=0; i--) {
deque<Word>::iterator p = words.begin();
words.insert(p + i1, vw[i]);
}
}
// ===========================================================================
// Merge words i1 through i2 inclusive into one word located at i1, remove
// words i1+1 through i2 inclusive.
// ===========================================================================
void Cmd::merge_words(int i1, int i2)
{
int lnum = words[i1].get_line_number();
int file_lnum = words[i1].get_file_line_number();
string fname = words[i1].get_filename();
string s = words[i1].get_string();
for (int i=i1+1; i<=i2; i++) {
s += words[i].get_string();
}
Word w(s, lnum, file_lnum, fname, lines);
replace_words(i1, i2, w);
}
// ===========================================================================
// Find the equals sign on the line.
// ===========================================================================
int Cmd::find_equals()
{
int ieq = -1;
for (int i=0; i<(int)words.size(); i++) {
if (words[i].get_string() == "=") {
ieq = i;
break;
}
}
return ieq;
}
// ***************************************************************************
// ***************************************************************************
// Handle processed flags.
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Set all the processed flags in the line to be false.
// ===========================================================================
void Cmd::clear_processed()
{
for (int i=0; i<(int)words.size(); i++) {
words[i].set_processed(false);
}
}
void Cmd::set_processed(bool ip)
{
for (int i=0; i<(int)words.size(); i++) {
words[i].set_processed(ip);
}
}
// ===========================================================================
// Check processed flags for each word.
// ===========================================================================
void Cmd::check_processed(bool &good, stringstream &serr, int &ierr)
{
// First we check to see if any of the words on the line have been
// processed. If none of the words have been processed, then we print
// the entire line as an error. This saves the user from having to
// wade through an error print for every word on the line.
bool p = false;
for (int i=0; i<(int)words.size(); i++) {
p = words[i].get_processed();
if (p) break;
}
if (!p) {
good = false;
words[0].fatal_error(serr, ierr);
serr << "This line has not been processed." << endl;
ierr = 2;
return;
}
// At least one word on the line has been processed.
// Check all the words on the line, throw an error for any word not
// processed.
for (int i=0; i<(int)words.size(); i++) {
p = words[i].get_processed();
if (!p) {
good = false;
words[i].fatal_error(serr, ierr);
serr << "A word on this line has not been processed." << endl;
serr << "Not proccessed word = " << words[i].get_string() << endl;
ierr = 2;
}
}
}
// ***************************************************************************
// ***************************************************************************
// Debug
// ***************************************************************************
// ***************************************************************************
// ===========================================================================
// Print all the words on the line mainly for debugging.
// Output is to a stringstream so the calling method can decide what to do
// with the output - send it to the screen, use it for testing, whatever.
// ===========================================================================
void Cmd::print_all_words()
{
stringstream ss;
print_all_words(ss);
cout << ss.str();
}
void Cmd::print_all_words(stringstream &ss)
{
ss << "*** Command name = " << cmd_name << endl;
for (int i=0; i<(int)words.size(); i++) {
stringstream ss2;
words[i].print_type(ss2);
ss << words[i].get_string() << " " << ss2.str() << endl;
}
ss << endl;
}
// ===========================================================================
// Print all the words on the line mainly for debugging.
// Output is to a stringstream so the calling method can decide what to do
// with the output - send it to the screen, use it for testing, whatever.
// ===========================================================================
void Cmd::print_using_words(stringstream &ss)
{
for (int i=0; i<(int)words.size(); i++) {
bool enc_quote = true;
if (i == 0) enc_quote = false;
ss << words[i].get_print_string(enc_quote) << " ";
}
}
// ===========================================================================
// Another version of printing all the words on the line.
// This version is mainly for printing out the final cmds buffer.
//
// The output is formatted, commas are put back in, spaces are handled better,
// if a line is too long (see nctot_max), it is split into more than one line.
//
// For example, suppose the words on a line were
// a4d ( 1 1 1 2 ) = -3.4 4.7 5.2 4.6e19
// spaces are used to delimit the words, but it is not very readable. This
// routine will print the above line as
// a4d(1, 1, 1, 2) = -3.4, 4.7, 5.2, 4.6e19
// This has the added advantage that spaces can be eliminated and a compact
// form can be achieved.
//
// Another example is the line
// strinsert_cmd01 = Use The Force
// The string "Use The Force" is actually one word, even though it appears to
// be three words. This routine prints this correctly as
// strinsert_cmd01 = "Use The Force"
// ===========================================================================
void Cmd::print_using_words_fm(stringstream &ss)
{
//debug_print_words("print_using_words_fm");
int nctot_max = 75;
int istart = 0;
int ieq = -1;
int ip1 = -1;
int ip2 = -1;
for (;;) {
if (istart > 0) ss << " ";
int nctot = 0;
bool done = false;
for (int i=istart; i<(int)words.size(); i++) {
bool enc_quote = true;
if (i == 0) enc_quote = false;
string s = words[i].get_print_string(enc_quote);
if (s == "=") ieq = i;
if (s == "(") ip1 = i;
if (s == ")") ip2 = i;
string sp = "";
if (i<(int)words.size()-1) {
sp = words[i+1].get_print_string(enc_quote);
}
if (sp == ")") ip2 = i+1;
int nc = (int)s.size();
if ((i>istart) && (nc+1+nctot > nctot_max)) {
istart = i;
break;
}
if (i >= (int)words.size()-1) {
done = true;
ss << s;
}
else {
string endstr = " ";
int endinc = 1;
if (ieq > -1) {
if (i > ieq) {
endstr = ", ";
endinc = 2;
}
}
if (ip1 > -1 && (ip2 == -1 || i < ip2)) {
if (i > ip1 && (ip2 == -1 || i < ip2-1)) {
endstr = ", ";
endinc = 2;
}
}
if (endstr == " ") {
if (s == "(") {
endstr = "";
endinc = 0;
}
if (i == ip2-1) {
endstr = "";
endinc = 0;
}
if (i == 0 && sp == "(") {
endstr = "";
endinc = 0;
}
}
ss << s << endstr;
nctot += nc + endinc;
}
}
if (done) break;
ss << endl;
}
}
// ===========================================================================
// Print the original command before processing, mainly for debugging.
// Output is to a stringstream so the calling method can decide what to do
// with the output - send it to the screen, use it for testing, whatever.
// ===========================================================================
void Cmd::print_original_string(stringstream &ss)
{
ss << original_str;
}
// ===========================================================================
// This is mainly for debugging this class. It prints on all procs.
// ===========================================================================
void Cmd::debug_print_words(string s)
{
cout << s << endl;
cout << " ";
for (int i=0; i<(int)words.size(); i++) {
bool enc_quote = true;
if (i == 0) enc_quote = false;
cout << words[i].get_print_string(enc_quote) << " ";
}
cout << endl;
}
} // End of the PP namespace