-
Notifications
You must be signed in to change notification settings - Fork 3
/
README
246 lines (180 loc) · 9.19 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
===============================================================================
=== RPN: A simple Reverse Polish Notation parser and evaluator. ==============
===============================================================================
By Kevin Burk
Licensed under version 3 of the GNU General Public License
--- Table of Contents ---------------------------------------------------------
1. Introduction
2. The Basics
3. Expressions
4. Contexts
5. Custom Nodes
--- 1. Introduction -----------------------------------------------------------
This is RPN. It's a Reverse Polish Notiation library. If you don't know what
Reverse Polish Notation is, you should check it out on Wikipedia:
https://en.wikipedia.org/wiki/Reverse_polish_notation
Reverse Polish Notation is a format that's very easy for computers to work
with. But it's not what humans are used to. So if you ask a user to enter a
formula, they'll give it to you in Infix Notation (3 + 4). But your computer
would much rather evaluate that expression in Reverse Polish Notation (3 4 +).
That's where RPN comes in: Give it strings in Infix Notation, and it'll turn
them into easily-evaluable Reverse Polish Notation expressions. It uses an
algorithm called the Shunting Yard Algorithm:
https://en.wikipedia.org/wiki/Shunting_yard_algorithm
And then, it'll evaluate them for you, too. Simple as that.
--- 2. The Basics -------------------------------------------------------------
So how do you, a programmer, use RPN?
First, you're going to have to have it installed. See the INSTALL file in this
directory for more on that.
Then, include RPN so your compiler knows what you're working with:
#include <rpn.h>
Initialize RPN. More on what this does in the "Contexts" section; for now,
just do it:
RPN::initialize();
You'll want to make an Expression, which is the most important (well, to you)
component of the library. Give it an infix string when you construct it to
create an evaluable expression right away:
RPN::Expression expr("1 + 2 - 3");
And then, you can evaluate that expression, and get your surprise answer
(spoiler: it's zero):
std::cout << expr.evaluate();
And that's that. It's not very useful, obviously, if you know the expression
you're evaluating beforehand, but hook it up to some command-line input and
you've got a simple calculator (in fact, I've done this; check out src/calc.cpp
and/or bin/calc.out). Or make something exen more complex...
It does a few extra tricks, too. For them, read on.
--- 3. Expressions ------------------------------------------------------------
As I said above: Expressions are what you're going to be working with the
most. The convenience constructor is a good place to start:
RPN::Expression expr("2 + 2");
But you can also create an empty expression, and/or parse a new string into an
existing expression. You can do this as many times as youlike; just be aware
that the parse function clears out the current expression:
RPN::Expression expr;
expr->parse("1 + 1");
expr->parse("2 * 2");
That convenience constructor and the parse function also take two optional
arguments: a Context and a Format.
The Format argument lets you select what format your input string is in. It
defaults to INFIX, but you can also specify POSTFIX to parse expressions that
are already in Reverse Polish Notation (note that currently, negative numbers
in POSTFIX mode must be specified using the negation operator (3~), rather than
the conventional notation (-3)). PREFIX parsing may come in a future release.
The Context argument lets you specify what "Context" your string is parsed in,
and for that, we'll need to move on to:
--- 4. Contexts ---------------------------------------------------------------
A Context is a dictionary of all the functions and operators that can be used
in a parsed string. There's a default Context stored as RPN::Context::ROOT;
this is what RPN::initialize() sets up, and that's why you want to call it -
it lets you use all the basics (sin, cos, +, -, ln, etc.) without having to
define them yourself.
The second argument to RPN::Expression's parsing functions (the convenience
constructor and parse()) is a Context. If you don't specify anything, it
defaults to the root context.
You can make your own contexts, though, and specify those instead:
RPN::Context cxt;
RPN::Expression expr("7 % 3", cxt);
Contexts have parents. They fit together into a tree structure. If you don't
want your context to be a child of the root context, you can specify a
different parent when you create it:
RPN::Context cxt;
RPN::Context subcxt(&cxt);
RPN::Expression expr("7 % 3", subcxt);
Why would you want to do this? Well, you can create your own functions and
operators (see "Custom Nodes"), but it'd be bad form to add these to the root
context (and in future versions, it may be impossible). So make your own
context, and add them there instead.
You can use this to make new functions and operators, or override the defaults.
This can be useful:
Say you want to parse some strings: str1 and str2. In str1, the sin() function
expects values in degrees, but in str2 sin() expects radians (RPN's default).
What do you do? Make a custom DecimalSineNode class (again, see "Custom
Nodes"), make a custom context to hold it, and insert your custom node. Then
you can parse str1 in your custom context, and parse str2 in the (unpoluted)
root context:
RPN::Context dectrig;
dectrig.insert("sin", new DecimalSineNode());
RPN::Expression expr1(str1, dectrig);
RPN::Expression expr2(str2);
And how do you define that custom node? Read on...
--- 5. Custom Nodes -----------------------------------------------------------
You can make a variety of custom nodes. There are two broad categories to
choose from: Values and Functions/Operators.
Values are the simplest nodes to add. You don't need to define any classes to
use them. Just use their constructors.
And Constants are the simplest of the Values. They're just numbers. You can't
change them. But they can be convenient. Let's say you're working with
something that uses the Golden Ratio a lot - it's easiest to define it once,
accurately, and then be able to refer to it in shorthand:
RPN::Context cxt;
cxt.insert("phi", new RPN::ConstantNode(1.61803398874));
Expression expr("(phi^3 - (1-phi)^3) / sqrt(5)", cxt);
Variables are the next simplest nodes. They're pointers to numbers, so you can
change the underlying value between evaluations:
double x = 7;
RPN::Context cxt;
cxt.insert("x", new RPN::VariableNode(&x));
Expression expr("x^2 + x - 1", cxt);
std::cout << expr.evaluate(); // prints 55
x = 12;
std::cout << expr.evaluate(); // prints 155
The last type of Value node is the Expression node. It does what you'd expect:
lets you reference one expression, by name, in another expression. The
referenced expression is evaluated every time the referencing expression is, so
if it changes (if parse() is called on it, say), this change will be apparent
on the next call to evaluate():
RPN::Context cxt;
RPN::Expression expr1("(1 + 3) * 10");
cxt.insert("expr1", new ExpressionNode(&expr1));
RPN::Expression expr2("myexpr + 7", cxt);
std::cout << expr2.evaluate(); // prints 47
expr1.parse("(1 + 3) * 9");
std::cout << expr2.evaluate(); // prints 43
Then there are the Function and Operator nodes. These let you do more
interesting things, but you'll have to do a little programming to implement
them.
Custom Functions should inherit from the RPN::FunctionNode class. This takes
care of most of the bookeeping for you. You only need to supply two things: an
argument count, which you pass into the RPN::FunctionNode constructor, and a
const evaluate() member function, which takes an RPN::Evaluator reference as
its one argument and returns a double. And within that function, make sure you
pop() all the arguments you use off of the Evaluator (they'll be in reverse
order):
class ClampNode : public FunctionNode
{
public:
ClampNode(): FunctionNode(3)
{
//Nothing else to do...
}
double evaluate(RPN::Evaluator& evaluator) const
{
double arg3 = evaluator.pop();
double arg2 = evaluator.pop();
double arg1 = evaluator.pop();
double max1 = (arg3 > arg2)? arg3 : arg2;
double max2 = (arg2 > arg1)? arg2 : arg1;
return (max1 < max2)? max1 : max2;
}
};
Custom Operators should inherit from the RPN::OperatorNode class. That works
just about the same, but the RPN::OperatorNode constructor takes different
arguments: a precedence, an associativity (this defaults to LEFT), and a number
of arguments (which defaults to 2). Besides that, though, it's just like
implementing a Function:
class ReciprocalNode : public OperatorNode
{
public:
ReciprocalNode(): OperatorNode(
OperatorNode::ADDITION,
OperatorNode::RIGHT,
1)
{
//Nothing else to do...
}
double evaluate(RPN::Evaluator& evaluator) const
{
double arg = evaluator.pop();
return 1.0 / arg;
}
};