Skip to content

Instantly share code, notes, and snippets.

@realchonk
Created January 8, 2022 17:59
Show Gist options
  • Save realchonk/13dfbd98be338dc1437ef19369af36cc to your computer and use it in GitHub Desktop.
Save realchonk/13dfbd98be338dc1437ef19369af36cc to your computer and use it in GitHub Desktop.
A simpler than pycalc1 calculator written in Python3.
#!/usr/bin/env python3
#
# Licensed under the MIT License.
#
# Copyright (c) 2022 Benjamin Stürz <[email protected]>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# This python3 script implements a basic calculator.
# It is a simpler version of pycalc1.
# which used a combination of lexing and parsing the input before evaluating it.
# This is a much simpler version that evaluates the input directly.
enable_color_output = True
#--------------------------------------------------------------------------------
# Input reading basics.
def read():
if pos >= len(line):
return '\0'
return line[pos]
def peek():
global pos
while read().isspace():
pos += 1
return read()
def next():
global pos
ch = peek()
pos += 1
return ch
def matches(x):
return peek() == x
def match(x):
global pos
b = matches(x)
if b: pos += 1
return b
def expect(x):
if not match(x):
error(f"Expected '{x}', got '{peek()}'")
#--------------------------------------------------------------------------------
# Error handling.
def asColorStr(text, color):
if enable_color_output:
return f"\033[{color}m{text}\033[0m"
else:
return text
def error(text):
print((' ' * (pos + 2)) + asColorStr('^: ' + text, 31))
raise RuntimeError("Error")
#--------------------------------------------------------------------------------
# Parsing & Evaluation of expressions.
# This parses basic integers and sub-expressions.
def factor():
if match('('):
val = expression()
expect(')')
elif peek().isdigit():
val = 0
while peek().isdigit():
val = val * 10 + (ord(next()) - ord('0'))
else:
error("Expected '(' or number.")
return val
# This parses factors with an optional +/- prefix.
def unary():
if matches('+') or matches('-'):
op = next()
val = unary()
if op == '-':
val = -val
return val
else:
return factor()
# This parses multiplications and divisions.
def term():
left = unary()
while matches('*') or matches('/'):
op = next()
right = unary()
if op == '*':
left *= right
else:
left /= right
return left
# This parses additions and subtractions.
def expression():
left = term()
while matches('+') or matches('-'):
op = next()
right = term()
if op == '+':
left += right
else:
left -= right
return left
#--------------------------------------------------------------------------------
if __name__ == "__main__":
while True:
try:
line = input("> ")
except EOFError:
break
except KeyboardInterrupt:
print()
continue
if line == 'exit':
break
elif len(line) == 0 or line.isspace():
continue
pos = 0
try:
e = expression()
except:
continue
print(e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment