├── README.md └── calc.py /README.md: -------------------------------------------------------------------------------- 1 | howCode Python Calculator Source Code 2 | ------------------------------------- 3 | This is the source code of the howCode Python Calculator series. The playlist for the series can be found here [http://howco.de/calc-playlist](http://howco.de/calc-playlist). 4 | 5 | If you have any questions please email francis@howcode.org. 6 | -------------------------------------------------------------------------------- /calc.py: -------------------------------------------------------------------------------- 1 | import ply.lex as lex 2 | import ply.yacc as yacc 3 | import sys 4 | 5 | # Create a list to hold all of the token names 6 | tokens = [ 7 | 8 | 'INT', 9 | 'FLOAT', 10 | 'NAME', 11 | 'PLUS', 12 | 'MINUS', 13 | 'DIVIDE', 14 | 'MULTIPLY', 15 | 'EQUALS' 16 | 17 | ] 18 | 19 | # Use regular expressions to define what each token is 20 | t_PLUS = r'\+' 21 | t_MINUS = r'\-' 22 | t_MULTIPLY = r'\*' 23 | t_DIVIDE = r'\/' 24 | t_EQUALS = r'\=' 25 | 26 | # Ply's special t_ignore variable allows us to define characters the lexer will ignore. 27 | # We're ignoring spaces. 28 | t_ignore = r' ' 29 | 30 | # More complicated tokens, such as tokens that are more than 1 character in length 31 | # are defined using functions. 32 | # A float is 1 or more numbers followed by a dot (.) followed by 1 or more numbers again. 33 | def t_FLOAT(t): 34 | r'\d+\.\d+' 35 | t.value = float(t.value) 36 | return t 37 | 38 | # An int is 1 or more numbers. 39 | def t_INT(t): 40 | r'\d+' 41 | t.value = int(t.value) 42 | return t 43 | 44 | # A NAME is a variable name. A variable can be 1 or more characters in length. 45 | # The first character must be in the ranges a-z A-Z or be an underscore. 46 | # Any character following the first character can be a-z A-Z 0-9 or an underscore. 47 | def t_NAME(t): 48 | r'[a-zA-Z_][a-zA-Z_0-9]*' 49 | t.type = 'NAME' 50 | return t 51 | 52 | # Skip the current token and output 'Illegal characters' using the special Ply t_error function. 53 | def t_error(t): 54 | print("Illegal characters!") 55 | t.lexer.skip(1) 56 | 57 | # Build the lexer 58 | lexer = lex.lex() 59 | 60 | # Ensure our parser understands the correct order of operations. 61 | # The precedence variable is a special Ply variable. 62 | precedence = ( 63 | 64 | ('left', 'PLUS', 'MINUS'), 65 | ('left', 'MULTIPLY', 'DIVIDE') 66 | 67 | ) 68 | 69 | # Define our grammar. We allow expressions, var_assign's and empty's. 70 | def p_calc(p): 71 | ''' 72 | calc : expression 73 | | var_assign 74 | | empty 75 | ''' 76 | print(run(p[1])) 77 | 78 | def p_var_assign(p): 79 | ''' 80 | var_assign : NAME EQUALS expression 81 | ''' 82 | # Build our tree 83 | p[0] = ('=', p[1], p[3]) 84 | 85 | # Expressions are recursive. 86 | def p_expression(p): 87 | ''' 88 | expression : expression MULTIPLY expression 89 | | expression DIVIDE expression 90 | | expression PLUS expression 91 | | expression MINUS expression 92 | ''' 93 | # Build our tree. 94 | p[0] = (p[2], p[1], p[3]) 95 | 96 | def p_expression_int_float(p): 97 | ''' 98 | expression : INT 99 | | FLOAT 100 | ''' 101 | p[0] = p[1] 102 | 103 | def p_expression_var(p): 104 | ''' 105 | expression : NAME 106 | ''' 107 | p[0] = ('var', p[1]) 108 | 109 | # Output to the user that there is an error in the input as it doesn't conform to our grammar. 110 | # p_error is another special Ply function. 111 | def p_error(p): 112 | print("Syntax error found!") 113 | 114 | def p_empty(p): 115 | ''' 116 | empty : 117 | ''' 118 | p[0] = None 119 | 120 | # Build the parser 121 | parser = yacc.yacc() 122 | # Create the environment upon which we will store and retreive variables from. 123 | env = {} 124 | # The run function is our recursive function that 'walks' the tree generated by our parser. 125 | def run(p): 126 | global env 127 | if type(p) == tuple: 128 | if p[0] == '+': 129 | return run(p[1]) + run(p[2]) 130 | elif p[0] == '-': 131 | return run(p[1]) - run(p[2]) 132 | elif p[0] == '*': 133 | return run(p[1]) * run(p[2]) 134 | elif p[0] == '/': 135 | return run(p[1]) / run(p[2]) 136 | elif p[0] == '=': 137 | env[p[1]] = run(p[2]) 138 | return '' 139 | elif p[0] == 'var': 140 | if p[1] not in env: 141 | return 'Undeclared variable found!' 142 | else: 143 | return env[p[1]] 144 | else: 145 | return p 146 | 147 | # Create a REPL to provide a way to interface with our calculator. 148 | while True: 149 | try: 150 | s = input('>> ') 151 | except EOFError: 152 | break 153 | parser.parse(s) 154 | --------------------------------------------------------------------------------