Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implemented parsing (string -> Basic) #772

Merged
merged 19 commits into from Feb 11, 2016
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -60,6 +60,7 @@ symengine/tests/basic/test_series_generic
symengine/tests/basic/test_subs
symengine/tests/basic/test_rational
symengine/tests/basic/test_as_numer_denom
symengine/tests/basic/test_parser
symengine/tests/matrix/test_matrix
symengine/tests/ntheory/test_ntheory
symengine/tests/ntheory/test_diophantine
Expand Down
1 change: 1 addition & 0 deletions symengine/CMakeLists.txt
Expand Up @@ -39,6 +39,7 @@ set(SRC
expression.cpp
numer_denom.cpp
derivative.cpp
parser.cpp
)

if (WITH_MPFR)
Expand Down
242 changes: 242 additions & 0 deletions symengine/parser.cpp
@@ -0,0 +1,242 @@
#include <symengine/basic.h>
#include <symengine/symbol.h>
#include <symengine/add.h>
#include <symengine/integer.h>
#include <symengine/rational.h>
#include <symengine/complex.h>
#include <symengine/mul.h>
#include <symengine/pow.h>
#include <symengine/functions.h>
#include <symengine/constants.h>
#include <symengine/visitor.h>
#include <vector>
#include <stack>
#include <map>
#include <set>
#include <string>
#include <functional>
namespace SymEngine {

class ExpressionParser
{
std::set<char> OPERATORS = {'-', '+', '*', '/', '^', '(', ')', ','};
std::map<char, int> op_precedence = {
{')', 0}, {',', 0}, {'-', 1}, {'+', 1},
{'*', 3}, {'/', 4}, {'^', 5}
};
std::map<std::string, RCP<const Basic> > constants = {
{"e", E}, {"EulerGamma", EulerGamma}, {"pi", pi}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add I as a constant too. Then complex numbers will be supported as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh didn't think about that! Thanks!

};

// reference : http://stackoverflow.com/questions/30393285/stdfunction-fails-to-distinguish-overloaded-functions
typedef RCP<const Basic> (*single_arg_func)(const RCP<const Basic>&);
typedef RCP<const Basic> (*double_arg_func)(const RCP<const Basic>&, const RCP<const Basic>&);

single_arg_func single_casted_log = log; // as they are overloaded
single_arg_func single_casted_zeta = zeta;

std::map< std::string,
std::function<RCP<const Basic>(const RCP<const Basic>&)>
> single_arg_functions = {

{"sin", sin}, {"cos", cos}, {"tan", tan},
{"cot", cot}, {"csc", csc}, {"sec", sec},

{"arcsin", asin}, {"arccos", acos}, {"arctan", atan},
{"arcsec", asec}, {"arccsc", acsc}, {"arccot", acot},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the original name. i.e. {"asin", asin}. Less confusion that way


{"sinh", sinh}, {"cosh", cosh}, {"tanh", tanh},
{"coth", coth}, // implement sech, csch

{"arcsinh", asinh}, {"arccosh", acosh}, {"arctanh", atanh},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

{"arcsech", asech}, {"arccoth", acoth}, // implement acsch

{"gamma", gamma}, {"sqrt", sqrt}, {"abs", abs}, {"exp", exp},
{"lambertw", lambertw}, {"dirichlet_eta", dirichlet_eta},
{"ln", single_casted_log}, {"log", single_casted_log}, {"zeta", single_casted_zeta}
};

double_arg_func double_casted_log = log; // as they are overloaded
double_arg_func double_casted_zeta = zeta;

std::map< std::string,
std::function<RCP<const Basic>(const RCP<const Basic>&, const RCP<const Basic>&)>
> double_arg_functions = {

{"log", double_casted_log}, {"zeta", double_casted_zeta},
{"pow", pow}, {"beta", beta}, {"uppergamma", uppergamma},
{"lowergamma", lowergamma}, {"kronecker_delta", kronecker_delta},
{"polygamma", polygamma}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these can probably be made const, since they never change once they are declared.


std::vector<int> operator_end;
std::string s;
unsigned int s_len;

RCP<const Basic> parse_string(unsigned int l, unsigned int h)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you think of any case where it might be useful to expose this by making parse_string public? I didn't see this in the tests. If not, it would be better to keep it private.

{
RCP<const Basic> result;
bool result_set = false;
bool is_not_numeric = false;
std::string expr = "";

for (unsigned int iter = l; iter < h; ++iter)
{
if (is_operator(iter))
{
if (s[iter] != '(')
if (!result_set)
result = set_result(expr, is_not_numeric);

switch(s[iter])
{
case '+':
result = add(result, parse_string(iter+1, operator_end[iter]));
iter = operator_end[iter]-1;
break;
case '*':
result = mul(result, parse_string(iter+1, operator_end[iter]));
iter = operator_end[iter]-1;
break;
case '-':
result = sub(result, parse_string(iter+1, operator_end[iter]));
iter = operator_end[iter]-1;
break;
case '/':
result = div(result, parse_string(iter+1, operator_end[iter]));
iter = operator_end[iter]-1;
break;
case '^':
result = pow(result, parse_string(iter+1, operator_end[iter]));
iter = operator_end[iter]-1;
break;
case '(':
result = functionify(iter, expr);
break;
case ')':
continue;
case ',':
continue;
}

result_set = true;
is_not_numeric = false;
}
else
{
expr += s[iter];

int ascii = s[iter] - '0';
if (ascii < 0 or ascii > 9)
is_not_numeric = true;

if (iter == h-1)
result = set_result(expr, is_not_numeric);
}
}

return result;
}

bool is_operator(int iter)
{
if (iter >= 0 and iter < (int)s_len)
if (OPERATORS.find(s[iter]) != OPERATORS.end())
return true;
return false;
}

RCP<const Basic> functionify(unsigned int& iter, std::string expr)
{
RCP<const Basic> param1 = parse_string(iter+1, operator_end[iter]);
iter = operator_end[iter] - 1;

if(expr == "") return param1;

if (single_arg_functions.find(expr) != single_arg_functions.end())
if (s[iter+1] != ',')
return single_arg_functions[expr](param1);

iter++;
RCP<const Basic> param2 = parse_string(iter+1, operator_end[iter]);
iter = operator_end[iter] - 1;

if (double_arg_functions.find(expr) != double_arg_functions.end())
return double_arg_functions[expr](param1, param2);

throw std::runtime_error("Unknown function " + expr);
// remaining : levi_civita
}

RCP<const Basic> set_result(std::string &expr, bool& is_not_numeric)
{
if (expr == "") return zero;

if (is_not_numeric)
{
if (constants.find(expr) != constants.end())
return constants[expr];
return symbol(expr);
}
else
return integer(std::atoi(expr.c_str()));
}

public:

RCP<const Basic> parse(std::string &in)
{
std::stack<unsigned int> right_bracket;
std::stack<std::pair<int, unsigned int> > op_stack;
s = "";

for (unsigned int i = 0; i < in.length(); ++i)
{
if (in[i] == ' ')
continue;
s += in[i];
}

s_len = s.length();
operator_end.clear();
operator_end.resize(s_len);
op_stack.push(std::make_pair(-1, s_len));

for (int i = s_len-1; i >= 0; i--)
{
if (is_operator(i))
{
char x = s[i];
if(x == '(')
{
while(op_stack.top().second != right_bracket.top())
op_stack.pop();
operator_end[i] = right_bracket.top();
right_bracket.pop();
op_stack.pop();
}
else if(x == ')' or x == ',')
{
if (x == ',')
{
operator_end[i] = right_bracket.top();
right_bracket.pop();
}
op_stack.push(std::make_pair(op_precedence[x], i));
right_bracket.push(i);
}
else
{
while(op_precedence[x] < op_stack.top().first)
op_stack.pop();

operator_end[i] = op_stack.top().second;
op_stack.push(std::make_pair(op_precedence[x], i));
}
}
}
return parse_string(0, s_len);
}
};

} // SymEngine
4 changes: 4 additions & 0 deletions symengine/tests/basic/CMakeLists.txt
Expand Up @@ -53,3 +53,7 @@ add_test(test_number ${PROJECT_BINARY_DIR}/test_number)
add_executable(test_as_numer_denom test_as_numer_denom.cpp)
target_link_libraries(test_as_numer_denom symengine catch)
add_test(test_as_numer_denom ${PROJECT_BINARY_DIR}/test_as_numer_denom)

add_executable(test_parser test_parser.cpp)
target_link_libraries(test_parser symengine catch)
add_test(test_parser ${PROJECT_BINARY_DIR}/test_parser)