| 1 | #!/usr/bin/env python
 | 
| 2 | # Copyright 2016 Andy Chu. All rights reserved.
 | 
| 3 | # Licensed under the Apache License, Version 2.0 (the "License");
 | 
| 4 | # you may not use this file except in compliance with the License.
 | 
| 5 | # You may obtain a copy of the License at
 | 
| 6 | #
 | 
| 7 | #   http://www.apache.org/licenses/LICENSE-2.0
 | 
| 8 | """
 | 
| 9 | arith_parse_test.py: Tests for arith_parse.py
 | 
| 10 | """
 | 
| 11 | 
 | 
| 12 | import unittest
 | 
| 13 | 
 | 
| 14 | from core import expr_eval
 | 
| 15 | from core import legacy
 | 
| 16 | from core import word_eval
 | 
| 17 | from core import state
 | 
| 18 | from core import test_lib
 | 
| 19 | 
 | 
| 20 | from osh import parse_lib
 | 
| 21 | #from osh import arith_parse
 | 
| 22 | 
 | 
| 23 | 
 | 
| 24 | class ExprSyntaxError(Exception):
 | 
| 25 |   pass
 | 
| 26 | 
 | 
| 27 | 
 | 
| 28 | def ParseAndEval(code_str):
 | 
| 29 |   arena = test_lib.MakeArena('<arith_parse_test.py>')
 | 
| 30 |   w_parser, _ = parse_lib.MakeParserForCompletion(code_str, arena)
 | 
| 31 |   anode = w_parser._ReadArithExpr()  # need the right lex state?
 | 
| 32 | 
 | 
| 33 |   if not anode:
 | 
| 34 |     raise ExprSyntaxError("failed %s" % w_parser.Error())
 | 
| 35 | 
 | 
| 36 |   print('node:', anode)
 | 
| 37 | 
 | 
| 38 |   mem = state.Mem('', [], {}, None)
 | 
| 39 |   exec_opts = state.ExecOpts(mem)
 | 
| 40 |   splitter = legacy.SplitContext(mem)
 | 
| 41 |   ev = word_eval.CompletionWordEvaluator(mem, exec_opts, splitter)
 | 
| 42 | 
 | 
| 43 |   arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, ev)
 | 
| 44 |   value = arith_ev.Eval(anode)
 | 
| 45 |   return value
 | 
| 46 | 
 | 
| 47 | 
 | 
| 48 | def testEvalExpr(e, expected):
 | 
| 49 |   print('expression:', e)
 | 
| 50 |   actual = ParseAndEval(e)
 | 
| 51 |   if actual != expected:
 | 
| 52 |     raise AssertionError('%s => %r, expected %r' % (e, actual, expected))
 | 
| 53 | 
 | 
| 54 | 
 | 
| 55 | def testSyntaxError(ex):
 | 
| 56 |   try:
 | 
| 57 |     actual = ParseAndEval(ex)
 | 
| 58 |   except ExprSyntaxError as e:
 | 
| 59 |     print(ex, '\t\t', e)
 | 
| 60 |   else:
 | 
| 61 |     raise AssertionError('Expected parse error: %r, got %r' % (ex, actual))
 | 
| 62 | 
 | 
| 63 | 
 | 
| 64 | class ArithTest(unittest.TestCase):
 | 
| 65 | 
 | 
| 66 |   def testEval(self):
 | 
| 67 |     testEvalExpr('(7)', 7)
 | 
| 68 | 
 | 
| 69 |     # Doesn't matter if you eval 2-3 first here
 | 
| 70 |     testEvalExpr('1 + 2 - 3', 0)
 | 
| 71 | 
 | 
| 72 |     testEvalExpr('1 + 2 * 3', 7)
 | 
| 73 | 
 | 
| 74 |     testEvalExpr('7 - 9 * (2 - 3)', 16)
 | 
| 75 |     testEvalExpr('2 * 3 * 4', 24)
 | 
| 76 | 
 | 
| 77 |     testEvalExpr('2 ** 3 ** 4', 2 ** (3 ** 4))
 | 
| 78 | 
 | 
| 79 |     testEvalExpr('(2 ** 3) ** 4', 4096)
 | 
| 80 | 
 | 
| 81 |     testEvalExpr('5', 5)
 | 
| 82 |     testEvalExpr('4 + 2', 6)
 | 
| 83 |     testEvalExpr('9 - 8 - 7', -6)
 | 
| 84 |     testEvalExpr('9 - (8 - 7)', 8)
 | 
| 85 |     testEvalExpr('(9 - 8) - 7', -6)
 | 
| 86 |     testEvalExpr('2 + 3 ** 2 * 3 + 4', 33)
 | 
| 87 | 
 | 
| 88 |     testEvalExpr('4 * 3 / 2', 6)
 | 
| 89 |     testEvalExpr('3 * 2 % 4', 2)
 | 
| 90 |     testEvalExpr('+ 1', 1)
 | 
| 91 |     testEvalExpr('- 5', -5)
 | 
| 92 |     testEvalExpr('-2-3', -5)
 | 
| 93 | 
 | 
| 94 |     # Comma has lower precedence
 | 
| 95 |     testEvalExpr('1 ? 2 : 3, 4 ? 5 : 6', 5)
 | 
| 96 | 
 | 
| 97 |     # Two commas
 | 
| 98 |     testEvalExpr('1 , 2, 3', 3)
 | 
| 99 | 
 | 
| 100 |     # TODO: fix the rest
 | 
| 101 |     return
 | 
| 102 | 
 | 
| 103 |     testEvalExpr('0 = 2 > 3', 0)  # => 0 > 3 => 0
 | 
| 104 | 
 | 
| 105 |     # string becomes integer"
 | 
| 106 |     #testEvalExpr(['ab21xx', ':', '[^0-9]*([0-9]*)', '+', '3'], 24)
 | 
| 107 | 
 | 
| 108 |     #testEvalExpr(['ab21xx', ':', '[^0-9]*([0-9]*)'], '21')
 | 
| 109 | 
 | 
| 110 |     #testEvalExpr(['ab21xx', ':', '(.)'], 'a')
 | 
| 111 | 
 | 
| 112 |     # integer becomes string.  -3 as a string is less than '-3-'
 | 
| 113 |     #testEvalExpr(['2', '-', '5', '<', '-3-'], True)
 | 
| 114 |     #testEvalExpr(['2', '-', '5', '<', '-2-'], False)
 | 
| 115 | 
 | 
| 116 |     # Arithmetic compare
 | 
| 117 |     testEvalExpr(['-3', '<', '-2'], True)
 | 
| 118 |     # String compare
 | 
| 119 |     #testEvalExpr(['-3', '<', '-2-'], False)
 | 
| 120 | 
 | 
| 121 |     #testEvalExpr(['3a', ':', '(.)', '=', '1', '+', '2'], True)
 | 
| 122 | 
 | 
| 123 |     # More stuff:
 | 
| 124 |     # expr / => /  -- it's just a plain string
 | 
| 125 |     # expr / : . => 1
 | 
| 126 |     # expr \( / \) => /   -- this is dumb
 | 
| 127 |     # expr + match => match  -- + is escaping, wtf
 | 
| 128 |     #
 | 
| 129 |     #testEvalExpr(['ab21xx', ':', '[^0-9]*([0-9]*)', '+', '3'], 24)
 | 
| 130 | 
 | 
| 131 |   def testEvalConstants(self):
 | 
| 132 |     # Octal constant
 | 
| 133 |     testEvalExpr('011', 9)
 | 
| 134 | 
 | 
| 135 |     # Hex constant
 | 
| 136 |     testEvalExpr('0xA', 10)
 | 
| 137 | 
 | 
| 138 |     # Arbitrary base constant
 | 
| 139 |     testEvalExpr('64#z', 35)
 | 
| 140 |     testEvalExpr('64#Z', 61)
 | 
| 141 |     testEvalExpr('64#@', 62)
 | 
| 142 |     testEvalExpr('64#_', 63)
 | 
| 143 | 
 | 
| 144 |   def testErrors(self):
 | 
| 145 |     # Now try some bad ones
 | 
| 146 | 
 | 
| 147 |     testSyntaxError('')
 | 
| 148 |     testSyntaxError(')')
 | 
| 149 |     testSyntaxError('(')
 | 
| 150 | 
 | 
| 151 |     # If ( is an error, I think this should be too.
 | 
| 152 |     # For now left it out.  Don't want to look up opinfo.
 | 
| 153 |     #
 | 
| 154 |     # In GNU expr, this is an error because + is escaping char!
 | 
| 155 |     #testSyntaxError('+')
 | 
| 156 |     #testSyntaxError('/')
 | 
| 157 | 
 | 
| 158 |     testSyntaxError('()')  # Not valid, expr also fails.
 | 
| 159 |     testSyntaxError('( 1')
 | 
| 160 |     testSyntaxError('(1 + (3 * 4)')
 | 
| 161 |     testSyntaxError('(1 + (3 * 4) 5')  # Not valid, expr also fails.
 | 
| 162 |     #testSyntaxError('1 1')
 | 
| 163 |     #testSyntaxError('( 1 ) ( 2 )')
 | 
| 164 | 
 | 
| 165 |     # NOTE: @ implicitly ends it now
 | 
| 166 |     #testSyntaxError('1 @ 2')
 | 
| 167 | 
 | 
| 168 | 
 | 
| 169 | if __name__ == '__main__':
 | 
| 170 |   unittest.main()
 |