| 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()
|