| 1 | #!/usr/bin/env python2
 | 
| 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 _devbuild.gen.types_asdl import lex_mode_e
 | 
| 15 | from core import error
 | 
| 16 | from core import test_lib
 | 
| 17 | from core import ui
 | 
| 18 | from osh import sh_expr_eval
 | 
| 19 | from osh import split
 | 
| 20 | from osh import word_eval
 | 
| 21 | from core import state
 | 
| 22 | 
 | 
| 23 | #from osh import arith_parse
 | 
| 24 | 
 | 
| 25 | 
 | 
| 26 | def ParseAndEval(code_str):
 | 
| 27 |     arena = test_lib.MakeArena('<arith_parse_test.py>')
 | 
| 28 |     parse_ctx = test_lib.InitParseContext(arena=arena)
 | 
| 29 |     w_parser = test_lib.InitWordParser(code_str, arena=arena)
 | 
| 30 | 
 | 
| 31 |     # This is weird but works
 | 
| 32 |     w_parser._SetNext(lex_mode_e.Arith)  # Calling private method
 | 
| 33 |     anode = w_parser.a_parser.Parse()
 | 
| 34 | 
 | 
| 35 |     print('node:', anode)
 | 
| 36 | 
 | 
| 37 |     mem = state.Mem('', [], arena, [])
 | 
| 38 |     parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, None)
 | 
| 39 |     mem.exec_opts = exec_opts
 | 
| 40 |     state.InitMem(mem, {}, '0.1')
 | 
| 41 | 
 | 
| 42 |     splitter = split.SplitContext(mem)
 | 
| 43 |     errfmt = ui.ErrorFormatter()
 | 
| 44 | 
 | 
| 45 |     tilde_ev = word_eval.TildeEvaluator(mem, exec_opts)
 | 
| 46 |     word_ev = word_eval.CompletionWordEvaluator(mem, exec_opts, mutable_opts,
 | 
| 47 |                                                 tilde_ev, splitter, errfmt)
 | 
| 48 | 
 | 
| 49 |     arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, mutable_opts,
 | 
| 50 |                                            parse_ctx, arena)
 | 
| 51 | 
 | 
| 52 |     arith_ev.word_ev = word_ev
 | 
| 53 |     return arith_ev.EvalToInt(anode)
 | 
| 54 | 
 | 
| 55 | 
 | 
| 56 | def testEvalExpr(e, expected):
 | 
| 57 |     print('expression:', e)
 | 
| 58 |     actual = ParseAndEval(e)
 | 
| 59 |     if actual != expected:
 | 
| 60 |         raise AssertionError('%s => %r, expected %r' % (e, actual, expected))
 | 
| 61 | 
 | 
| 62 | 
 | 
| 63 | def testSyntaxError(expr):
 | 
| 64 |     try:
 | 
| 65 |         actual = ParseAndEval(expr)
 | 
| 66 |     except error.Parse as e:
 | 
| 67 |         print(expr, '\t\t', e)
 | 
| 68 |     else:
 | 
| 69 |         raise AssertionError('Expected parse error: %r, got %r' %
 | 
| 70 |                              (expr, actual))
 | 
| 71 | 
 | 
| 72 | 
 | 
| 73 | class ArithTest(unittest.TestCase):
 | 
| 74 | 
 | 
| 75 |     def testEval(self):
 | 
| 76 |         testEvalExpr('(7)', 7)
 | 
| 77 | 
 | 
| 78 |         # Doesn't matter if you eval 2-3 first here
 | 
| 79 |         testEvalExpr('1 + 2 - 3', 0)
 | 
| 80 | 
 | 
| 81 |         testEvalExpr('1 + 2 * 3', 7)
 | 
| 82 | 
 | 
| 83 |         testEvalExpr('7 - 9 * (2 - 3)', 16)
 | 
| 84 |         testEvalExpr('2 * 3 * 4', 24)
 | 
| 85 | 
 | 
| 86 |         testEvalExpr('2 ** 3 ** 4', 2**(3**4))
 | 
| 87 | 
 | 
| 88 |         testEvalExpr('(2 ** 3) ** 4', 4096)
 | 
| 89 | 
 | 
| 90 |         testEvalExpr('5', 5)
 | 
| 91 |         testEvalExpr('4 + 2', 6)
 | 
| 92 |         testEvalExpr('9 - 8 - 7', -6)
 | 
| 93 |         testEvalExpr('9 - (8 - 7)', 8)
 | 
| 94 |         testEvalExpr('(9 - 8) - 7', -6)
 | 
| 95 |         testEvalExpr('2 + 3 ** 2 * 3 + 4', 33)
 | 
| 96 | 
 | 
| 97 |         testEvalExpr('4 * 3 / 2', 6)
 | 
| 98 |         testEvalExpr('3 * 2 % 4', 2)
 | 
| 99 |         testEvalExpr('+ 1', 1)
 | 
| 100 |         testEvalExpr('- 5', -5)
 | 
| 101 |         testEvalExpr('-2-3', -5)
 | 
| 102 | 
 | 
| 103 |         # Comma has lower precedence
 | 
| 104 |         testEvalExpr('1 ? 2 : 3, 4 ? 5 : 6', 5)
 | 
| 105 | 
 | 
| 106 |         # Two commas
 | 
| 107 |         testEvalExpr('1 , 2, 3', 3)
 | 
| 108 | 
 | 
| 109 |         # TODO: fix the rest
 | 
| 110 |         return
 | 
| 111 | 
 | 
| 112 |         testEvalExpr('0 = 2 > 3', 0)  # => 0 > 3 => 0
 | 
| 113 | 
 | 
| 114 |         # string becomes integer"
 | 
| 115 |         #testEvalExpr(['ab21xx', ':', '[^0-9]*([0-9]*)', '+', '3'], 24)
 | 
| 116 | 
 | 
| 117 |         #testEvalExpr(['ab21xx', ':', '[^0-9]*([0-9]*)'], '21')
 | 
| 118 | 
 | 
| 119 |         #testEvalExpr(['ab21xx', ':', '(.)'], 'a')
 | 
| 120 | 
 | 
| 121 |         # integer becomes string.  -3 as a string is less than '-3-'
 | 
| 122 |         #testEvalExpr(['2', '-', '5', '<', '-3-'], True)
 | 
| 123 |         #testEvalExpr(['2', '-', '5', '<', '-2-'], False)
 | 
| 124 | 
 | 
| 125 |         # Arithmetic compare
 | 
| 126 |         testEvalExpr(['-3', '<', '-2'], True)
 | 
| 127 |         # String compare
 | 
| 128 |         #testEvalExpr(['-3', '<', '-2-'], False)
 | 
| 129 | 
 | 
| 130 |         #testEvalExpr(['3a', ':', '(.)', '=', '1', '+', '2'], True)
 | 
| 131 | 
 | 
| 132 |         # More stuff:
 | 
| 133 |         # expr / => /  -- it's just a plain string
 | 
| 134 |         # expr / : . => 1
 | 
| 135 |         # expr \( / \) => /   -- this is dumb
 | 
| 136 |         # expr + match => match  -- + is escaping, wtf
 | 
| 137 |         #
 | 
| 138 |         #testEvalExpr(['ab21xx', ':', '[^0-9]*([0-9]*)', '+', '3'], 24)
 | 
| 139 | 
 | 
| 140 |     def testEvalConstants(self):
 | 
| 141 |         # Octal constant
 | 
| 142 |         testEvalExpr('011', 9)
 | 
| 143 | 
 | 
| 144 |         # Hex constant
 | 
| 145 |         testEvalExpr('0xA', 10)
 | 
| 146 | 
 | 
| 147 |         # Arbitrary base constant
 | 
| 148 |         testEvalExpr('64#z', 35)
 | 
| 149 |         testEvalExpr('64#Z', 61)
 | 
| 150 |         testEvalExpr('64#@', 62)
 | 
| 151 |         testEvalExpr('64#_', 63)
 | 
| 152 | 
 | 
| 153 |     def testErrors(self):
 | 
| 154 |         # Now try some bad ones
 | 
| 155 | 
 | 
| 156 |         testSyntaxError('')
 | 
| 157 |         testSyntaxError(')')
 | 
| 158 |         testSyntaxError('(')
 | 
| 159 | 
 | 
| 160 |         # If ( is an error, I think this should be too.
 | 
| 161 |         # For now left it out.  Don't want to look up opinfo.
 | 
| 162 |         #
 | 
| 163 |         # In GNU expr, this is an error because + is escaping char!
 | 
| 164 |         #testSyntaxError('+')
 | 
| 165 |         #testSyntaxError('/')
 | 
| 166 | 
 | 
| 167 |         testSyntaxError('()')  # Not valid, expr also fails.
 | 
| 168 |         testSyntaxError('( 1')
 | 
| 169 |         testSyntaxError('(1 + (3 * 4)')
 | 
| 170 |         testSyntaxError('(1 + (3 * 4) 5')  # Not valid, expr also fails.
 | 
| 171 | 
 | 
| 172 |         testSyntaxError(';')
 | 
| 173 |         testSyntaxError('- ;')
 | 
| 174 | 
 | 
| 175 |         #testSyntaxError('1 1')
 | 
| 176 |         #testSyntaxError('( 1 ) ( 2 )')
 | 
| 177 | 
 | 
| 178 |         # NOTE: @ implicitly ends it now
 | 
| 179 |         #testSyntaxError('1 @ 2')
 | 
| 180 | 
 | 
| 181 | 
 | 
| 182 | if __name__ == '__main__':
 | 
| 183 |     unittest.main()
 |