50 lines of Python code to create a calculator

Source: Internet
Author: User

Introduction

In this article, I will show you how to parse and calculate a four arithmetic expression just like a general calculator. When we end, we will get a calculator that can process expressions such as 1 + 2 *-(-3 + 2)/5.6 + 3. Of course, you can also expand it more powerful.

I wanted to provide a simple and interesting course to explain the syntax analysis and regular syntax compilation principles ). At the same time, let's introduce PlyPlus, which is an interface that I intermittently improved for several years. As an additional product of this course, we will finally get a secure four-timer that completely replaces eval.

If you want to try the example given in this article on your computer, you should first install PlyPlus and use the command pip install plyplus. Note: pip is a package management system used to install software packages written in python. You can use it on Baidu or google .)

This article needs to understand the use of python inheritance.

Syntax

For those who do not understand how to parse and work with formal syntax, here is a quick overview: formal syntax is used to parse different levels of text rules. Each rule describes how the text of the corresponding part of the input is made up.

Here is an example of how to parse 1 + 2 + 3 + 4:

 
 
  1. Rule #1 - add  IS MADE OF  add + number   
  2.                        OR  number + number 

Or use EBNF:

 
 
  1. add: add'+'number  
  2.    | number'+'number  
  3.    ; 

The parser searches for add + number or number + number every time. After finding one, it converts it to add. Basically, the goal of each parser is to find the highest level of expression abstraction as much as possible.

Each step of the parser is as follows:

1. number + numberThe first conversion converts all numbers into "Number" rules.

2. [number + number] + numberThe parser finds its first matching mode!

3. [add + number] + numberAfter converting to a mode, it starts to look for the next

4. [add + number]

5. add

These ordered symbols become two simple rules at a level: number + number and add + number. In this way, you only need to tell the computer that if the two problems are solved, it can parse the entire expression. In fact, no matter how long the addition sequence can be solved! This is the power of formal grammar.

Operator priority

Arithmetic expressions are not just linear growth of symbols. Operators create an implicit hierarchy, which is very suitable for representation using formal syntax:

1 + 2*3/4-5 + 6

This is equivalent:

1 + (2*3/4)-5 + 6

We can use nested rules to indicate the structure in this syntax:

 
 
  1. add: add+mul  
  2.    | mul'+'mul  
  3.    ;  
  4. mul: mul '*; number  
  5.    | number'*'number  
  6.    ; 

By setting add as an operation mul rather than a number, we get a multiplication priority rule. Let's simulate the process of using this magic parser to analyze 1 + 2*3*4:

1. number + number * number

2. number + [number * number] * numberThe parser does not know the result of number + number, so this is another choice of its parser)

3. number + [Mul* Number]

4. number +Mul

5 .???

Now we have encountered some difficulties! The parser does not know how to handle number + mul. We can distinguish this situation, but if we continue to explore, we will find that there are many different possibilities, such as mul + number, add + number, add + add, and so on.

So what should we do?

Fortunately, we can do a little trick: We can think that a number is a product, and a product is a sum!

This idea looks a bit odd at the beginning, but it does make sense:

 
 
  1. add: add'+'mul  
  2.    | mul'+'mul  
  3.    | mul  
  4.    ;  
  5. mul: mul'*'number  
  6.    | number'*'number  
  7.    | number  
  8.    ; 

However, if mul can be changed to add and the number can be changed to mul, the content of some rows will become redundant. Discard them and we will get:

 
 
  1. add: add'+'mul  
  2.    | mul  
  3.    ;  
  4. mul: mul'*'number  
  5.    | number  
  6.    ; 

Let's use this new syntax to simulate 1 + 2*3*4:

1. number + number * numberCurrently, no rule corresponds to number * number, but the parser can "become creative"

2. number + [number] * number

3. number + [mul * number] * number

4. number + [mul * number]

5. [number] + mul

6. [mul] + mul

7. [add + mul]

8. add

Success !!!

If you think this is amazing, try to use another arithmetic expression to simulate and run it, and then see how the expression solves the problem step by step in the correct way. Or wait to read the content in the next section and see how the computer runs step by step!

Run the parser

Now we have a very good idea about how to make our syntax work, so let's write a practical syntax to apply it:

 
 
  1. Start: add; // This is the highest level
  2. Add: add add_symbol mul | mul;
  3. Mul: mul mul_symbol number | number;
  4. Number: '[d.] +'; // Regular Expression of the decimal number
  5. Mul_symbol: '*' | '/'; // Match * or/
  6. Add_symbol: '+' | '-'; // Match + or-

You may want to review regular expressions, but in any case, this syntax is very straightforward. Let's test it with an expression:

 
 
  1. >>>fromplyplusimportGrammar  
  2. >>> g=Grammar("""...""")  
  3. >>>printg.parse('1+2*3-5').pretty()  
  4. start  
  5.   add  
  6.     add  
  7.       add  
  8.         mul  
  9.           number  
  10.             1 
  11.       add_symbol  
  12.         +  
  13.       mul  
  14.         mul  
  15.           number  
  16.             2 
  17.         mul_symbol  
  18.           *  
  19.         number  
  20.           3 
  21.     add_symbol  
  22.       -  
  23.     mul  
  24.       number  
  25.         5 

Pretty good!

Take a closer look at this tree to see what level the parser chooses.

If you want to run the parser and use your own expressions, you only need to have Python. After installing Pip and PlyPlus, paste the preceding command into Python (replace '...' with the actual syntax ~).

Build trees

Plyplus automatically creates a tree, but it is not necessarily optimal. Adding numbers to mul and adding mul to add is very helpful for creating a class. Now we already have a class which will become a burden. We told Plyplus to prefix them to "Expand" I. e. delete) Rules.

When a @ is used to develop a rule, a # rule is flattened, and? It is expanded when it has a child knot. In this case ,? Is what we need.

 
 
  1. start: add;  
  2. ?add: add add_symbol mul | mul;      // Expand add if it's just a mul  
  3. ?mul: mul mul_symbol number | number;// Expand mul if it's just a number  
  4. number:'[d.]+';  
  5. mul_symbol:'*'|'/';  
  6. add_symbol:'+'|'-'; 

In the new syntax, the tree is like this:

 
 
  1. >>> g=Grammar("""...""")  
  2. >>>printg.parse('1+2*3-5').pretty()  
  3. start  
  4.   add  
  5.     add  
  6.       number  
  7.         1 
  8.       add_symbol  
  9.         +  
  10.       mul  
  11.         number  
  12.           2 
  13.         mul_symbol  
  14.           *  
  15.         number  
  16.           3 
  17.     add_symbol  
  18.       -  
  19.     number  
  20.       5 

Oh, this is much more concise, I dare say, it is very good.

Brackets and other features

So far, we have obviously lacked some necessary features: parentheses, cell operators (-(1 + 2), and expressions that allow null characters. In fact, these features can be easily implemented. Let's try it.

An important concept should be introduced first: Atom. All operations performed in parentheses and unit operations in an atom take precedence over all addition or multiplication operations, including bit operations ). Since the atom is only a priority constructor and has no syntax significance, it adds the "@" symbol to ensure that it can be expanded during compilation.

The simplest way to allow spaces to appear in an expression is to use this interpretation method: add SPACE add_symbol SPACE mul | mul; however, the interpretation results are too long and the readability is poor. All, we need to make Plyplus always ignore spaces.

The following is a complete syntax that covers the features described above:

 
 
  1. start: add;  
  2. ?add: (add add_symbol)? mul;  
  3. ?mul: (mul mul_symbol)? atom;  
  4. @atom: neg | number |'('add')';  
  5. neg:'-'atom;  
  6. number:'[d.]+';  
  7. mul_symbol:'*'|'/';  
  8. add_symbol:'+'|'-';  
  9. WHITESPACE:'[ t]+'(%ignore); 

Make sure you understand this syntax before proceeding to the next step: computing!

Operation

Now, we can convert an expression into a layered tree. We only need to scan the tree branch by branch to get the final result.

Now we are writing code. Before that, I need to explain this tree in two ways:

1. Each branch is an instance with the following attributes:

Header: Rule name (such as add or number );

Tail: contains a list of all matched sub-rules.

2. Plyplus removes unnecessary tags by default. In this example, '(', ')' and '-' are deleted. But add and mul have their own rules. Plyplus will know that they are required and will not be deleted. If you need to retain these tags, You can manually turn off this feature, but in my experience, it is better not to do this, but to manually modify the relevant syntax.

Now let's write the code. We will use a very simple converter to scan this tree. It starts scanning from the outermost branch until it reaches the root node, and our job is to tell it how to scan. If everything goes well, it will always start scanning from the outermost layer! Let's take a look at the specific implementation.

 
 
  1. >>>importoperator as op  
  2. >>>fromplyplusimportSTransformer  
  3. classCalc(STransformer):  
  4.    
  5.     def_bin_operator(self, exp):  
  6.         arg1, operator_symbol, arg2=exp.tail  
  7.    
  8.         operator_func={'+': op.add,  
  9.                           '-': op.sub,  
  10.                           '*': op.mul,  
  11.                           '/': op.div }[operator_symbol]  
  12.    
  13.         returnoperator_func(arg1, arg2)  
  14.    
  15.     number     =lambdaself, exp:float(exp.tail[0])  
  16.     neg        =lambdaself, exp:-exp.tail[0]  
  17.     __default__=lambdaself, exp: exp.tail[0]  
  18.    
  19.     add=_bin_operator  
  20.     mul=_bin_operator 

Each method corresponds to a rule. If the method does not exist, the _ default _ method is called. We omit start, add_symbol, and mul_symbol, because they only return their own branches.

I use float () to parse numbers. This is a lazy method, but I can also use a parser.

To make the statements clean and tidy, I use the operator module. For example, add is basically 'lambda x, y: x + Y.

OK. Now run this code to check the result.

 
 
  1. >>> Calc().transform( g.parse('1 + 2 * -(-3+2) / 5.6 + 30'))  
  2. 31.357142857142858 

What about eval? 7

 
 
  1. >>>eval('1 + 2 * -(-3+2) / 5.6 + 30')  
  2. 31.357142857142858 

Successful :)

Last step: REPL

To be beautiful, we encapsulate it into a good calculator REPL:

 
 
  1. defmain():  
  2.     calc=Calc()  
  3.     whileTrue:  
  4.         try:  
  5.             s=raw_input('> ')  
  6.         exceptEOFError:  
  7.             break 
  8.         ifs=='':  
  9.             break 
  10.         tree=calc_grammar.parse(s)  
  11.         printcalc.transform(tree) 

Complete code is available here: https://github.com/erezsh/plyplus/blob/master/examples/calc.py

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.