2023 - EE308FZ - Software Engineering
Assignment 2: Back-end separation calculator programming
Scientific Calculator Front-end & Back-end Development
Content
I. Introduction
Last time a simple front-end calculator was developed, however, the function of it is simple and crude for short of time. Today, a scientific calculator with front-end and back-end development is required. The scientific calculator should have the following functions:
1. Basic calculator functions:
Function 1
Basic operation includes addition (+), subtraction(-), multiplication(*), division (/), remainder (mod) operators with correct order and result are realized. In addition, with back-end database sqlite, the input string and its corresponding result can be saved.
Function 2
With an “ANS” button, the previous calculation formula and its corresponding results can be read from the database SQLite;
2. Extended function: Scientific calculator
Besides the above basic calculation rules, some added functions, including trigonometric functions, logarithm and exponential function, absolute function and factorial function, and bracket computation are also illustrated.
-Link to the project code:
https://github.com/ZhouWx2023/EE308FZ_SE_A2_improvedCalculator
II. Basic information about the author
The link of my class | https://bbs.csdn.net/forums/ssynkqtd-04 |
---|---|
The Link of Requirement of This Assignment | https://bbs.csdn.net/topics/617378696 |
The Aim of This Assignment | To create a more perfect calculator with front-end and back-end development. |
MU STU ID and FZU STU ID | 21126321 (MU) 832101116 (FZU) |
III. PSP Table
Personal Software Process Stages | Estimated Time(minutes) | Actual Time(minutes) |
---|---|---|
Planning | ||
• Estimate | 240 | 360 |
Development | ||
• Analysis | 60 | 60 |
• Design Spec | 120 | 120 |
• Design Review | 60 | 120 |
• Coding Standard | 30 | 60 |
• Design | 30 | 60 |
• Coding | 480 | 600 |
• Code Review | 60 | 120 |
• Test | 60 | 120 |
Reporting | ||
• Test Report | 0 | 0 |
• Size Measurement | 0 | 0 |
• Postmortem & Process Improvement Plan | 60 | 90 |
Sum | 1200 | 1710 |
IV. Ideas before launching the project
The program is distributed as our SOFTWARE ENGINEERING assignment 2. This time front-end and back-end development should be combined together, so the simple calculator developed last time by python and tkinter is not satisfied the requirement now. In order to exchange data between front-end and back-end, CSS, HTML and part of python bootstrap framework component are used for FRONT-END development, and Python flask frameworks and SQLite database are used for BACK-END development. The connection between front-end and back-end are realized by json data transfer.
V. The process of design and implementation
1. The appearance and function of the scientific calculator design
Three popular tools for front-end development are used here for the appearance of Web scientific calculator here. The snapshot of the Web scientific calculator is given in the following Fig.1.
Appearance
Figure 1
Functions
(1) Basic functions: the operator button ‘+’ ‘-‘ ‘*’ ‘/’ and ‘mod’ are used for basic operation, including brackets ‘(‘ and ‘)‘ to ensure the correct calculation order.
(2) e / pi / sin / cos / tan / ln / log / |x| / n! / x-1 / x1/2 / x2 / xy are extended functions for user.
(3) ‘CLEAR’ means clear the input field and reset the calculator status.
(4) ‘ANS’ is the button for showing the history of the previously input formula and result.
(5) ‘Req.’ is the link to show the requirement of our assignment 2.
(6) ‘Blog’ is the link to show the blog of my project details.
2. Block Diagram of the Scientific Calculator
Block diagram and data flow of the system scheme is given as follows (Fig.2):
Block Diagram
Figure 2
VI. Architecture and code description of the scheme
(1) Architecture and key modules of the scheme
Architecture and key modules of the scheme is given in the Fig.3
Figure 3
(2) Data processing flow for operation request
Data processing flow for operation request is shown in the following figure (Fig.4).
Figure 4
(3) Code description
▪ index.html
<!DOCTYPE html>
<html>
<head>
<title>Calculator from Zhou WX (Second Edition)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
<link href='https://fonts.googleapis.com/css?family=Orbitron' rel='stylesheet' type='text/css'>
<link rel="shortcut icon" href="{{ url_for('static', filename='icon.ico') }}" type="image/x-icon">
</head>
<body>
<div class="box" id="0">
<div class="top">
<div class="screen col-md-2">
<div class="main-screen" contenteditable="true" tabindex="0" id="output">0</div>
<div class="sub-screen" id="output2"></div>
</div>
<div class="buttons col-md-2">
<button class="btn-clear btn btn-danger" id="clearButton">CLEAR</button>
<button class="show-hist btn btn-info" title="Past done"
data-container="body" data-toggle="popover" data-placement="bottom"
data-content="Null">ANS</button>
</div>
</div>
<div class="container-fluid">
<div class="buttons">
<button class="btn btn-warning" onclick="window.open('https://bbs.csdn.net/topics/617378696')">Req.</button>
<button class="btn btn-warning" onclick="window.open('https://blog.csdn.net/ZhouWenxuan1116/article/details/133553063?spm=1001.2014.3001.5502')">Blog</button>
<button class="btn-operate btn btn-primary" value="/">/</button>
<button class="btn-operate btn btn-primary" value="*">*</button>
<button class="btn-func btn btn-primary" value="<sup>{-1}</sup>" id=>X<sup>-1</sup></button>
<button class="btn-func btn btn-primary" value="<sup>{2}</sup>">X<sup>2</sup></button>
</div>
<div class="buttons">
<button class="nums btn btn-primary" value="(">(</button>
<button class="btn-operate btn btn-primary" value=")">)</button>
<button class="btn-operate btn btn-primary" value="%">mod</button>
<button class="btn-operate btn btn-primary" value="!">n!</button>
<button class="btn-func btn btn-primary" value="<sup>{1/2}</sup>">X<sup>1/2</sup></button>
<button class="btn-func-sup btn btn-primary" value="<sup>{?}</sup>">X<sup>y</sup></button>
</div>
<div class="buttons">
<button class="nums btn btn-primary" value="7">7</button>
<button class="nums btn btn-primary" value="8">8</button>
<button class="nums btn btn-primary" value="9">9</button>
<button class="btn-operate btn btn-primary" value="-">-</button>
<button class="btn-func btn btn-primary" value="abs">|X|</button>
<button class="btn-func btn btn-primary" value="tan">tan</button>
</div>
<div class="buttons">
<button class="nums btn btn-primary" value="4">4</button>
<button class="nums btn btn-primary" value="5">5</button>
<button class="nums btn btn-primary" value="6">6</button>
<button class="btn-operate btn btn-primary" value="+">+</button>
<button class="btn-func btn btn-primary" value="cos">cos</button>
<button class="btn-func btn btn-primary" value="sin">sin</button>
</div>
<div class="buttons">
<button class="nums btn btn-primary" value="1">1</button>
<button class="nums btn btn-primary" value="2">2</button>
<button class="nums btn btn-primary" value="3">3</button>
<button class="btn-func btn btn-primary" value="ln">ln</button>
<button class="btn-func btn btn-primary" value="log">log</button>
<button class="btn-equal btn btn-warning" id="resultButton">=</button>
</div>
<div class="buttons">
<button class="nums btn btn-primary" value="0">0</button>
<button class="nums btn btn-primary" value=".">.</button>
<button class="nums btn btn-primary" value="pi">蟺</button>
<button class="nums btn btn-primary" value="e">e</button>
<button class="btn-func-sup btn btn-primary" value="e<sup>{?}</sup>">Exp</button>
</div>
</div>
<footer class="text-center">
<p>Scientific Calculator for Assignment 2</p>
<p>Front-end & Back-end Development Project</p>
</footer>
</div>
<script src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<script src="http://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<script type="text/javascript">
var $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script>
</body>
</html>
▪ style.css
.box {
font-family: 'Arial', sans-serif;
width: 420px;
height: 420px;
margin: 5% auto auto;
background-color: lightgoldenrodyellow;
border: 1px solid #9e9b97;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), inset -1px -6px 12px 0.1px #89847e;
border-radius: 20px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.list-group {
background: transparent;
}
.screen {
height: 60px;
width: 280px;
border: 1px solid #7c877f;
background: #9e9b97;
/*background: #c7d3c5; */
margin: 20px 1px 10px 20px;
border-radius: 6px;
}
.main-screen {
width: 240px;
height: 22px;
padding: 10px 5px;
font-size: 20px;
text-align: left;
}
.sub-screen {
max-width: 280px;
height: 15px;
padding: 10px 5px;
font-size: 12px;
text-align: left;
}
.buttons {
margin: 10px auto;
}
button {
margin: 5px;
padding: 3px 10px;
width: 50px;
height: 30px;
border: none;
box-shadow: 1px 2px #666;
}
button:focus {outline:0 !important;}
button:active {
box-shadow: none;
transform: translateY(4px);
}
.btn-clear {
width: 70px;
}
.show-hist {
width: 70px;
}
.btn-equal {
position: absolute;
margin-left: 10px;
height: 75px;
}
footer {
font-size: 16px;
margin-bottom:5px
}
▪ main.js
$(document).ready(function() {
var sup_tag = false;
var mainOutput = $('#output');
var subOutput = $('#output2');
var history=[];
var clearOutput = function() {
mainOutput.html('');
subOutput.html('');
};
var digitError = function() {
mainOutput.html('0');
subOutput.html('Reach Digit Limit');
};
var checkInput = function () {
if (mainOutput.html().replace('<sup>','').replace('</sup>','').length > 18) {
digitError();
}
};
$(this).keydown( function(e) {
checkInput();
var key = window.event?e.keyCode:e.which;
if(key.toString() == "13"){
submit();
return false;
}
});
$(this).keydown( function(pi) {
checkInput();
var key = window.event?pi.keyCode:pi.which;
if(key.toString() == "13"){
submit();
return false;
}
});
$('.show-hist').click(function() {
$('.show-hist').popover({
html : true
});
});
var submit = function() {
if (mainOutput.html() === '' || ('+-*/').indexOf(mainOutput.html()) != -1) return ;
$.getJSON($SCRIPT_ROOT + '/_calculate', {
exp: $('.main-screen').text()
}, function(data) {
if (data.result.toString().length > 32) {
digitError();
} else {
if (data.result=='#'){
mainOutput.html('0');
subOutput.html('Input Error!');
return;
}
var exp = '<li>'+mainOutput.html()+'='+data.result+'</li>';
var content = '';
mainOutput.html(data.result);
// subOutput.html('result as below');
if (history.length == 0) {
history.push(exp);
}
else if (history.length < 10) {
history.unshift(exp);
}
else {
history.pop();
history.unshift(exp);
}
for (var i = 0;i < history.length;i++){
content += history[i];
}
$('.show-hist').attr('data-content',content);
}
});
};
$('.nums').click(function() {
if (sup_tag == true){
mainOutput.html(mainOutput.html().replace('?',$(this).val()));
sup_tag = false;
return;
}
// if ($(this).val() == '.' && (mainOutput.html()).indexOf('.') != -1) return ;
if (mainOutput.html() == '0' || subOutput.html() == 'Reach Digit Limit') {
clearOutput();
}
mainOutput.append($(this).val());
// subOutput.append($(this).val());
checkInput();
});
$('#clearButton').click(function() {
mainOutput.html('0');
subOutput.html('');
// clearData();
});
$('.btn-func').click(function() {
if (mainOutput.html() == '0' || subOutput.html() == 'Reach Digit Limit') {
clearOutput();
if ($(this).val().indexOf('1') != -1 || $(this).val().indexOf('2') != -1){
subOutput.html('Input Error!');
mainOutput.html('0');
return;
}
}
mainOutput.append($(this).val());
checkInput();
});
$('.btn-func-sup').click(function() {
if (mainOutput.html() == '0' || subOutput.html() == 'Reach Digit Limit') {
clearOutput();
if ($(this).val().indexOf('e')){
subOutput.html('Input Error!');
mainOutput.html('0');
return;
}
}
mainOutput.append($(this).val());
sup_tag = true;
checkInput();
});
$('.btn-operate').click(function() {
if (mainOutput.html() == '0' || subOutput.html() == 'Reach Digit Limit') {
clearOutput();
subOutput.html('Input Error!');
mainOutput.html('0');
return;
}
mainOutput.append($(this).val());
checkInput();
});
$('#resultButton').click(function() {
submit();
})
});
▪ calculate.py
# -*- coding: utf-8 -*-
import re, time, math
def math_sign(sign, par_ans):
if sign == 'cos':
par_ans = str(math.cos(math.radians(float(par_ans))))
elif sign == 'sin':
par_ans = str(math.sin(math.radians(float(par_ans))))
elif sign == 'tan':
par_ans = str(math.tan(math.radians(float(par_ans))))
elif sign == 'log':
par_ans = str(math.log(float(par_ans)) / math.log(10))
elif sign == 'ln':
par_ans = str(math.log(float(par_ans)))
elif sign == 'abs':
par_ans = str(abs(float(par_ans)))
return par_ans
def clean(string):
'''Judge if the data is illegal or not'''
b = 0
for i in string: # Judge
if b < 0: break
if i == "(":
b += 1
elif i == ")":
b -= 1
# character = re.search("[a-zA-Z\=]", string) # No letter is empty
kh = len(re.findall("\d+\.?\d*[\(]", string)) # judge if the bracket with data
kh1 = len(re.findall("[()]", string)) # judge if there is bracket
# dian = re.search("(\d+\.\.\d+)", string) # judge if the point is repeated ..
if kh1 % 2 == b == kh:
return string.replace(" ", "") # remove the space in the string
return 0
def sign_replace(string):
'''replace the sign'''
string = str(string)
string = string.replace("++", "+")
string = string.replace("+-", "-")
string = string.replace("-+", "-")
string = string.replace("--", "+")
string = string.replace("*+", "*")
string = string.replace("/+", "/")
return string
def ccf(xx):
'''Product and division'''
if re.search("\(", xx): xx = xx[1:-1] # remove the bracket
while re.search("[\*\/\%]", xx):
times = re.search("\d+\.?\d*[\*\/\%]{1}\-?\d+\.?\d*", xx)
if times:
times = times.group()
if times.count("*") == 1: # product operation
a, b = times.split("*")
xx = xx.replace(times, str(float(a) * float(b)))
elif times.count("/") == 1:
a, b = times.split("/") # division operation
xx = xx.replace(times, str(float(a) / float(b)))
elif times.count('%') == 1:
a, b = times.split("%") # mod operation
xx = xx.replace(times, str(int(a) % int(b)))
else:
return xx
return xx
def jjf(xx):
'''addition and substraction'''
if "(" in xx: xx = xx[1:-1] # remove bracket
while re.search("\d+\.?\d*[\+\-]\d+\.?\d*", xx):
findret = re.search("[\-]?\d+\.?\d*[\+\-]\d+\.?\d*", xx)
if findret:
findret = findret.group()
if re.search("\d+\.?\d*\+\d+\.?\d*", findret): # addition
a, b = findret.split("+")
xx = xx.replace(findret, str(float(a) + float(b)))
elif re.search("\d+\.?\d*\-\d+\.?\d*", findret): # substraction
a, b = findret.split("-")
xx = xx.replace(findret, str(float(a) - float(b)))
else:
return xx
return xx
def parre(string):
'''searching for the bracket'''
string = re.search("[a-z]*(\([^()]+\))", string)
if string: return string.group() # when bracket is found return the result
return 0 # if it is not found return zero
def expo(string):
string = re.search("[a-z0-9\.]*(\{[^{}]+\})", string)
if string: return string.group() # when exponent number is find return the result
return 0 # if it is not found return zero
def iter(string):
string = re.search("[0-9]*!", string)
if string: return string.group() # when the iteration sign is found return the result
return 0 # if it is not found return zero
def count(sample):
sample = clean(sample) # judge if the formula is illegal or not
sample = sample.replace('mod', '%')
sample = sample.replace('e', str(math.e))
sample = sample.replace('pi', str(math.pi))
if sample: # if it is legal then do the special extended function
while sample.count('!') > 0:
iters = iter(sample)
num = iters[:-1]
num = int(num)
temp = 1
for i in range(1, num + 1):
temp *= i
sample = sample.replace(iters, str(temp))
while sample.count('{') > 0: # dealing with the exponential number
exp = expo(sample)
exp_place = exp.find('{')
x = exp[0:exp_place]
exp_part = exp[exp_place:]
exp_ans = float(x) ** float(exp_part[1:-1])
sample = sample.replace(exp, str(exp_ans))
while sample.count("(") > 0: # if the bracket is kept on
par = parre(sample) # searching the bracket
if par[0] == '(':
sample = sample.replace(par, str(count(sign_replace(par[1:-1])))) # replace the bracket
else:
par_place = par.find('(')
sign = par[0:par_place]
par_part = par[par_place:]
par_ans = count(sign_replace(par_part[1:-1]))
ans = math_sign(sign, par_ans)
sample = sample.replace(par, ans)
else: # without bracket
ret = jjf(ccf(sign_replace(sample)))
if "+" in ret: ret = ret[1:] # get the sign before the positive number
while len(re.findall("\d+\.?\d*[\+\-\*\/\%]+\d+\.?\d*", ret)) > 0:
ret = jjf(ccf(sign_replace(ret)))
else:
print("The program is illegal, cannot be calculated")
return ret
▪ app.py
# -*- coding: utf-8 -*-
from calculate import *
from flask import Flask, jsonify, render_template, request
#import sqlite3 as sql
app = Flask(__name__)
num=0
conn = sqlite3.connect('hist.db') // connect to database
conn.execute('DROP TABLE histCal') // delete previous history table
conn.execute('CREATE TABLE histCal (id INT PRIMARY KEY NOT NULL, expr TEXT NOT NULL, results TEXT NOT NULL)') // create a new table
@app.route('/_calculate')
def calculate(): // calculation
exp = request.args.get('exp')
try:
result = count(exp)
except:
result = '#'
return jsonify(result=result)
def addtohist(): // add a new item to the table in database
num = num+1
exp = request.args.get('exp')
result = count(exp)
try:
with sqlite3.connect(hist.db) as con:
cur = con.cursor()
cur.execute("INSERT INTO histCal(id, expr, results) VALUES (?,?,?)",(num,exp,result))
con.commit()
msg = "data is added successfully"
except:
con.rollback()
msg= "data saving is failed."
finally:
conn.close()
def readlist(): // read all the items from the database
con = sql.connect("hist.db")
con.row_factory = sql.Row
cur = con.cursor()
cur.execute("select * from history")
rows = cur.fetchall();
return jsonify(history=rows)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
▪ Hist.db
The example of the content in the database is shown in the following figure. And the operation of the database is listed in the code of app.py.
VII. Project performance display
The result of the program is shown above, and the functions of addition, subtraction, multiplication and division, as well as some trigonometric, log functions, and other added functions are also illustrated. When “ANS” button is clicked, ten items of the history of formula expression input previously are read from the SQLite database and shown on the paste window.
VIII. Project disadvantages and future prospects
Because I never touch Web development tools before, when the scheme of the project is settled down, the first challenge comes from how to use HTML, CSS and Javascript to design a suitable calculator on Website. It takes me several days to learn the relationship and basic skills of those tools and finally construct a relatively good-looking scientific calculator appearance on my Website.
Then I learned how to use python flask framework to communicate with database, firstly I thought to use MySQL, it is a popular scheme for database construction. However, my computer storage is not enough, I tried to search alternative database – SQLite, It is a very tiny server, which is also supported by python flask framework, so it is my suitable choice.
When the front-end and database were all prepared, the biggest challenge was coming. How to transfer data from front-end to back-end is the hardest part of this project. After looking up so much experiences of others and teaching materials, I tried again and again, after hundreds of failures, finally the way to realize the data interaction between front-end and back-end were build up correctly.
IX. Summary
Through the implementation of this project, I tried to learn front-end development tools HTML / CSS / Javascript, and extended my python learning, such as flask framework with SQLite database connection. differences and data interaction between front-end and back-end platform. It is a painful journey full of challenges, fortunately I finished it before deadline. During this trip, I also experienced so much happiness of success by solving problems after hundreds of failures. In a word, it teaches me a lot, not only front-end and back-end development basic knowledges.