1/* Copyright (c) 2012 Massachusetts Institute of Technology
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to deal
5 * in the Software without restriction, including without limitation the rights
6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 * copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 * THE SOFTWARE.
20 */
21
22#include "Calculator.h"
23
24#include <cctype>
25#include <iostream>
26
27namespace LibUtil
28{
29    using std::cout;
30    using std::endl;
31    using std::scientific;
32
33    Calculator::Calculator()
34    {
35        m_reserved_chars_ = "+-*/;=()\\";
36    }
37
38    Calculator::~Calculator()
39    {}
40
41    void Calculator::reset()
42    {
43        m_var_.clear();
44        return;
45    }
46
47    void Calculator::evaluateString(const String& str_,
48                                    const map<String, String> &config,
49                                    DSENT::Model *ms_model,
50                                    map<string, double> &outputs)
51    {
52        istringstream ist(str_);
53
54        while(ist)
55        {
56            getToken(ist);
57            if(m_curr_token_ == END) break;
58            if(m_curr_token_ == SEP) continue;
59            if((m_curr_token_ == NAME) && (m_value_string_ == "print"))
60            {
61                getToken(ist);
62
63                if(m_curr_token_ == STRING)
64                {
65                    String print_str = m_value_string_;
66
67                    getToken(ist);
68                    if(m_curr_token_ == SEP)
69                    {
70                        outputs[print_str] = 0;
71                        cout << print_str << endl;
72                    }
73                    else
74                    {
75                        double v = expr(ist, false, config, ms_model);
76                        outputs[print_str] = v;
77                        cout << print_str << v << endl;
78                    }
79                }
80                else
81                {
82                    double v = expr(ist, false, config, ms_model);
83                    outputs["Missing Expression"] = v;
84                    cout << v << endl;
85                }
86            }
87            else
88            {
89                expr(ist, false, config, ms_model);
90            }
91        }
92    }
93
94    Calculator::Token Calculator::getToken(istringstream& ist_)
95    {
96        char ch;
97        do
98        {
99            ist_.get(ch);
100            if(!ist_)
101            {
102                m_curr_token_ = END;
103                return m_curr_token_;
104            }
105        }
106        while(ch != '\n' && isspace(ch));
107
108        switch(ch)
109        {
110            case '\n':
111                m_curr_token_ = END;
112                return m_curr_token_;
113            case ';':
114                m_curr_token_ = SEP;
115                return m_curr_token_;
116            case '*':
117            case '/':
118            case '+':
119            case '-':
120            case '(':
121            case ')':
122            case '=':
123                m_curr_token_ = Token(ch);
124                return m_curr_token_;
125            case '0': case '1': case '2': case '3': case '4':
126            case '5': case '6': case '7': case '8': case '9':
127            case '.':
128                ist_.putback(ch);
129                ist_ >> m_value_number_;
130                m_curr_token_ = NUMBER;
131                return m_curr_token_;
132            case '"':
133                ist_.get(ch);
134                m_value_string_ = "";
135                while(ist_ && ('"' != ch))
136                {
137                    m_value_string_ += String(1, ch);
138                    ist_.get(ch);
139                }
140                m_curr_token_ = STRING;
141                return m_curr_token_;
142            case '$':
143                ist_.get(ch);
144                ASSERT((ch == '('), "[Error] Bad token: '(' expected");
145                ist_.get(ch);
146                m_value_string_ = "";
147                while(ist_ && (!isspace(ch)) && (')' != ch))
148                {
149                    m_value_string_ += String(1, ch);
150                    ist_.get(ch);
151                }
152                m_curr_token_ = NAME2;
153                return m_curr_token_;
154            default:
155                if(isalpha(ch))
156                {
157                    m_value_string_ = ch;
158                    ist_.get(ch);
159                    while(ist_ && (isalnum(ch) || ('_' == ch)))
160                    {
161                        m_value_string_ += String(1, ch);
162                        ist_.get(ch);
163                    }
164                    ist_.putback(ch);
165                    m_curr_token_ = NAME;
166                    return m_curr_token_;
167                }
168                else
169                {
170                    String error_msg = "[Error] Bad token: '" + String(ch) + "'";
171                    throw Exception(error_msg);
172                }
173        }
174    }
175
176    double Calculator::prim(istringstream& ist_, bool is_get_,
177                            const map<String, String> &config,
178                            DSENT::Model *ms_model)
179    {
180        if(is_get_)
181        {
182            getToken(ist_);
183        }
184
185        double v;
186        switch(m_curr_token_)
187        {
188            case NUMBER:
189                v = m_value_number_;
190                getToken(ist_);
191                return v;
192            case NAME:
193                if(getToken(ist_) == ASSIGN)
194                {
195                    String var_name = m_value_string_;
196                    v = expr(ist_, true, config, ms_model);
197                    m_var_.set(var_name, v);
198                }
199                else
200                {
201                    v = m_var_.get(m_value_string_);
202                }
203                return v;
204            case NAME2:
205                v = getEnvVar(m_value_string_, config, ms_model);
206                getToken(ist_);
207                return v;
208            case MINUS:
209                return -prim(ist_, true, config, ms_model);
210            case LP:
211                v = expr(ist_, true, config, ms_model);
212                ASSERT((m_curr_token_ == RP), "[Error] ')' expected");
213                getToken(ist_);
214                return v;
215            default:
216                ASSERT(0, "[Error] primary expected, get: '" + String(int(m_curr_token_)) + "'");
217        }
218    }
219
220    double Calculator::term(istringstream& ist_, bool is_get_,
221                            const map<String, String> &config,
222                            DSENT::Model *ms_model)
223    {
224        double left = prim(ist_, is_get_, config, ms_model);
225
226        while(1)
227        {
228            double d;
229            switch(m_curr_token_)
230            {
231                case MUL:
232                    left *= prim(ist_, true, config, ms_model);
233                    break;
234                case DIV:
235                    d = prim(ist_, true, config, ms_model);
236                    ASSERT(d, "[Error] divided by 0");
237                    left /= d;
238                    break;
239                default:
240                    return left;
241            }
242        }
243    }
244
245    double Calculator::expr(istringstream& ist_, bool is_get_,
246                            const map<String, String> &config,
247                            DSENT::Model *ms_model)
248    {
249        double left = term(ist_, is_get_, config, ms_model);
250
251        while(1)
252        {
253            switch(m_curr_token_)
254            {
255                case PLUS:
256                    left += term(ist_, true, config, ms_model);
257                    break;
258                case MINUS:
259                    left -= term(ist_, true, config, ms_model);
260                    break;
261                default:
262                    return left;
263            }
264        }
265    }
266
267    double Calculator::getEnvVar(const String& var_name_,
268                                 const map<String, String> &config,
269                                 DSENT::Model *ms_model) const
270    {
271        return m_var_.get(var_name_);
272    }
273} // namespace LibUtil
274