[Experience compilation principles] compile a simple calculator and compile a principle Calculator
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.
This article takes the simplified version of the calculator as an example, using the token parsing and analysis method of the compilation principle, aims to let beginners understand and feel the basic thinking of the compilation principle.
[For more information, see http://www.codeproject.com/articles/246374/a-calculation-engine-for-net]
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 seriesToken). 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 resolvedExpressionMake 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 a higher priority operator
var l = ParseMulDiv ();
while (_token.Type == TokenType.Add || _token.Type == TokenType.Sub)
{
var t = _token.Type;
ParseToken ();
var r = ParseMulDiv (); // Parse right operand
l = new BinaryExpression (t, l, r);
}
return l;
}
// Parse multiplication and division
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 there is only a single integer)
Expression ParseUnary ()
{
Expression ret = null;
if (_token.Type == TokenType.Int)
{
ret = new UnaryExpress ((int) _token.Value);
}
// After parsing int, move to the next token, ie +-* /
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 operand is 1. When it is parsed to +, the program tries to parse the right operand, 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 the demo is thoroughly studied, you can try parentheses, modulo operations, and one-dimensional expressions (plus and 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 )).
Compilation Principle Design implementation of a simple calculator (without running an automatic generation tool using the syntax analyzer): To achieve addition, subtraction, multiplication, division, parentheses
Write a simple calculator in java
This is a program I wrote before. I have dropped the useless ones in it. If you want to add other functions, just unbind the comments.
Import java. awt .*;
Import java. awt. event .*;
Import javax. swing .*;
Public class Calculator extends JFrame {
Private Container container;
Private GridBagLayout layout;
Private GridBagConstraints constraints;
Private JTextField displayField; // computing result display area
Private String lastCommand; // save +,-, *,/, = command
Private double result; // Save the calculation result.
Private boolean start; // determines whether it is the start of a number.
Public Calculator (){
Super ("Calculator ");
Container = getContentPane ();
Layout = new GridBagLayout ();
Container. setLayout (layout );
Constraints = new GridBagConstraints ();
Start = true;
Result = 0;
LastCommand = "= ";
DisplayField = new JTextField (20 );
DisplayField. setHorizontalAlignment (JTextField. RIGHT );
Constraints. gridx = 0;
Constraints. gridy = 0;
Constraints. gridwidth = 4;
Constraints. gridheight = 1;
Constraints. fill = GridBagConstraints. BOTH;
Constraints. weightx = 100;
Constraints. weighty = 100;
Layout. setConstraints (displayField, constraints );
Container. add (displayField );
ActionListener insert = new InsertAction ();
ActionListener command = new CommandAction ();
// AddButton ("Backspace", 0, 1, 2, 1, insert );
// AddButton ("CE", 2, 1, 1, 1, insert );
// AddButton ("C", 3, 1, 1, 1, insert );
AddButton ("7", 0, 2, 1, 1, insert );
AddButton ("8", 1, 2, 1, 1, insert );
AddButton ("9", 2, 2, 1, 1, inser... the remaining full text>