Demo: caculationtest
Preface
Have you ever thought about writing a calculator yourself? Enter some mathematical expressions to calculate and parse the generated results.
If you don't have one, you can start to think about it now. Maybe you will find that it takes a few seconds for you to calculate the expression, which makes Program Computing not that simple.
Assume that
For ease of understanding, we need to simplify it now. The data type is only integer, and the operator only has addition, subtraction, multiplication, division, and no parentheses. Running result:
Parsing process
Analyze each char of the expression string one by one and parse it into a series of tokens. Then perform operations based on the different meanings of the token until the final result is calculated.
(In this example, not all tokens are parsed, and then the tokens are traversed. Instead, the tokens are parsed and operated. This is a little more efficient, but you cannot directly view all the tokens parsed .)
This is also close to our reading: We read information from left to right. During the reading process, we will form a Certain semantic according to the context, that is, the front and back, for example, "In fact, we cannot bear it ", you may understand it as "he really cannot bear it" or "He actually cannot bear it". You have to choose based on your understanding of the context.
In contrast to the metaphor above, we can conclude that token is the basic component of semantics, or an abstraction of character composition. The program abstracts the token into the following data structure:
enum TokenType { Add,Sub, Mul,Div, Int, Start,End } class Token { public TokenType Type; public object Value; public Token(TokenType type , object value = null) { Type = type; Value = value; } }
Token and Expression
The token must be parsed as an expression to make sense. With an expression, we can calculate the final result. An expression is a token or a group of tokens that can indicate a specific meaning.
In C #, we have a one-dimensional expression and a binary expression. The expression has different operations, such as addition and subtraction. In this example, all expressions can calculate a value, in addition, expressions can be calculated to form new expressions, such as "expressions (1*2) + expressions (2*3 )". Based on this, expression classes are not complex:
abstract class Expression { public abstract int GetValue(); } class UnaryExpress : Expression { int _value; public UnaryExpress(int value) { _value = value; } public override int GetValue() { return _value; } } class BinaryExpression : Expression { TokenType _tokenType; int _left; int _right; public BinaryExpression(TokenType tokenType, Expression left, Expression right) { _tokenType = tokenType; _left = left.GetValue(); _right = right.GetValue(); } public override int GetValue() { switch (_tokenType) { case TokenType.Add: return _left + _right; case TokenType.Sub: return _left - _right; case TokenType.Mul: return _left * _right; case TokenType.Div: return _left / _right; default: throw new Exception("unexceptional token!"); } } }
In this example, there is no "one-dimensional expression" in the true sense, just think of a number as it. The value calculation of binary expressions is relatively complex, but there are not many classes.
Algorithm priority
If it is not a bracket, I am afraid that the biggest trouble for us to use our own "original method" to parse the expression is to solve the problem of algorithm priority.
Why is "1 + 2*3" not resolved to 1 + 2 and multiplied by 3? How can we correctly resolve it to 2*3 and then add it to 1?
First, we need to analyze each token in sequence. The parsing order of the expression determines the final operation order. Let's look at the three methods that are important in the original code:
// Parse addition and subtraction expression parseaddsub () {// The left operand is the highest priority operator var L = parsemuldiv (); While (_ token. type = tokentype. add | _ token. type = tokentype. sub) {var T = _ token. type; parsetoken (); var r = parsemuldiv (); // parse the right operand L = new binaryexpression (T, L, R);} return l ;} // parse and multiply expression parsemuldiv () {var L = parseunary (); While (_ token. type = tokentype. mul | _ token. type = tokentype. div) {var T = _ token. type; parsetoken (); var r = parseunary (); L = new binaryexpression (T, L, R);} return l ;} // parse The unary expression (currently only one integer) expression parseunary () {expression ret = NULL; If (_ token. type = tokentype. INT) {ret = new unaryexpress (INT) _ token. value);} // After the int is parsed, move it to the next token, that is, +-*/parsetoken (); return ret ;}
1*2 + 2*2, we can think of it as adding two multiplication expressions. Therefore, before parsing the Left and Right operators of addition operations, the program tries to read whether it is a multiplication expression.
If it is 1 + 2*3, the left operator does not match when the multiplication operation is parsed. The value is determined by the highest priority unary expression, and 1 is returned, so the left operator is 1. When it is parsed to +, the program tries to parse the right operator, starting from the multiplication and division of the higher level of the Priority Ratio Method, and searching for matching. Obviously, 2*3 hits the multiplication expression. After calculating the result of the right operand and adding it to 1, the result is correct.
Exercise
If you have thoroughly studied the demo, you can try to use parentheses, modulo operations, and no expression (plus or minus signs ).
Extension
In the Il code and linqexpression API, "expressions", "binary expressions", "value assignment expressions", and "members get expressions (. operations) "and so on, are very common, there are also a lot of corresponding operations after the expression of resolution, new instances, reference instances, access local variables, assign values, etc. If you are interested, you can check the original dynamic LINQ code (in the subsequent sections, We will write a simple and easy version of dynamic LINQ dynamic computing iqueryable. Where (string )).
[Experience compilation principles] compile a simple calculator