| 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 | word_parse_test.py: Tests for word_parse.py
 | 
| 10 | """
 | 
| 11 | 
 | 
| 12 | import unittest
 | 
| 13 | 
 | 
| 14 | from _devbuild.gen.id_kind_asdl import Id, Id_str
 | 
| 15 | from _devbuild.gen.syntax_asdl import arith_expr_e, word_e, rhs_word_e
 | 
| 16 | from _devbuild.gen.types_asdl import lex_mode_e
 | 
| 17 | 
 | 
| 18 | from asdl import format as fmt
 | 
| 19 | from core import error
 | 
| 20 | from core import test_lib
 | 
| 21 | from core.test_lib import FakeTok
 | 
| 22 | from frontend import lexer
 | 
| 23 | from frontend import location
 | 
| 24 | from osh import word_
 | 
| 25 | 
 | 
| 26 | 
 | 
| 27 | def _assertReadWordWithArena(test, w_parser):
 | 
| 28 |     w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 29 |     assert w is not None
 | 
| 30 |     fmt.PrettyPrint(w)
 | 
| 31 |     print('')
 | 
| 32 | 
 | 
| 33 |     # Next word must be Eof_Real
 | 
| 34 |     w2 = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 35 |     test.assertTrue(test_lib.TokensEqual(FakeTok(Id.Eof_Real, ''), w2), w2)
 | 
| 36 |     return w
 | 
| 37 | 
 | 
| 38 | 
 | 
| 39 | def _assertReadWord(test, word_str, oil_at=False):
 | 
| 40 |     print('')
 | 
| 41 |     print('--- %s' % word_str)
 | 
| 42 |     print('')
 | 
| 43 | 
 | 
| 44 |     arena = test_lib.MakeArena('word_parse_test.py')
 | 
| 45 |     w_parser = test_lib.InitWordParser(word_str, arena=arena, oil_at=oil_at)
 | 
| 46 |     w = _assertReadWordWithArena(test, w_parser)
 | 
| 47 |     return w
 | 
| 48 | 
 | 
| 49 | 
 | 
| 50 | def _assertReadWordFailure(test, word_str, oil_at=False):
 | 
| 51 |     print('\n---', word_str)
 | 
| 52 |     w_parser = test_lib.InitWordParser(word_str, oil_at=oil_at)
 | 
| 53 |     try:
 | 
| 54 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 55 |     except error.Parse as e:
 | 
| 56 |         print('Got expected ParseError: %s' % e)
 | 
| 57 |     else:
 | 
| 58 |         fmt.PrettyPrint(w)
 | 
| 59 |         test.fail('Expected a parser error, got %r' % w)
 | 
| 60 | 
 | 
| 61 | 
 | 
| 62 | def _assertSpanForWord(test, word_str):
 | 
| 63 |     arena = test_lib.MakeArena('word_parse_test.py')
 | 
| 64 |     w_parser = test_lib.InitWordParser(word_str, arena=arena)
 | 
| 65 |     w = _assertReadWordWithArena(test, w_parser)
 | 
| 66 |     tok = location.LeftTokenForWord(w)
 | 
| 67 | 
 | 
| 68 |     print(word_str)
 | 
| 69 |     print(tok)
 | 
| 70 | 
 | 
| 71 | 
 | 
| 72 | def _GetSuffixOp(test, w):
 | 
| 73 |     """Get a single transform op."""
 | 
| 74 |     test.assertEqual(1, len(w.parts))
 | 
| 75 |     return w.parts[0].suffix_op
 | 
| 76 | 
 | 
| 77 | 
 | 
| 78 | def _GetPrefixOp(test, w):
 | 
| 79 |     """Get a single transform op."""
 | 
| 80 |     test.assertEqual(1, len(w.parts))
 | 
| 81 |     return w.parts[0].prefix_op.id
 | 
| 82 | 
 | 
| 83 | 
 | 
| 84 | def _GetVarSub(test, w):
 | 
| 85 |     test.assertEqual(1, len(w.parts))
 | 
| 86 |     part = w.parts[0]
 | 
| 87 |     return lexer.LazyStr(part.token)
 | 
| 88 | 
 | 
| 89 | 
 | 
| 90 | class ArenaTest(unittest.TestCase):
 | 
| 91 |     """It's more convenient to test the arena here, because we have a proper
 | 
| 92 |     lexer and so forth."""
 | 
| 93 | 
 | 
| 94 |     def testSnipCodeString(self):
 | 
| 95 |         expr = """\
 | 
| 96 | hi'
 | 
| 97 | single quoted'"double
 | 
| 98 | quoted
 | 
| 99 | "there
 | 
| 100 |     """
 | 
| 101 | 
 | 
| 102 |         arena = test_lib.MakeArena('hi')
 | 
| 103 |         w_parser = test_lib.InitWordParser(expr, arena=arena)
 | 
| 104 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 105 |         print(w)
 | 
| 106 | 
 | 
| 107 |         left = w.parts[1].left  # left single quote
 | 
| 108 |         right = w.parts[2].right  # right double quote
 | 
| 109 | 
 | 
| 110 |         s = arena.SnipCodeString(left, right)
 | 
| 111 | 
 | 
| 112 |         print('s = %r' % s)
 | 
| 113 |         self.assertEqual("""\
 | 
| 114 | '
 | 
| 115 | single quoted'"double
 | 
| 116 | quoted
 | 
| 117 | "\
 | 
| 118 | """, s)
 | 
| 119 | 
 | 
| 120 |         s = arena.SnipCodeString(w.parts[1].left, w.parts[1].right)
 | 
| 121 | 
 | 
| 122 |         print('s = %r' % s)
 | 
| 123 |         self.assertEqual("""\
 | 
| 124 | '
 | 
| 125 | single quoted'\
 | 
| 126 | """, s)
 | 
| 127 | 
 | 
| 128 |         # Just snip one token
 | 
| 129 |         s = arena.SnipCodeString(w.parts[0], w.parts[0])
 | 
| 130 | 
 | 
| 131 |         print('s = %r' % s)
 | 
| 132 |         self.assertEqual('hi', s)
 | 
| 133 | 
 | 
| 134 |     def testSaveLinesAndDiscard(self):
 | 
| 135 |         # Also takes a left, right, token
 | 
| 136 |         pass
 | 
| 137 | 
 | 
| 138 | 
 | 
| 139 | class LexerTest(unittest.TestCase):
 | 
| 140 |     """It's more convenient to test the lexer here, because we have a proper
 | 
| 141 |     lexer and so forth."""
 | 
| 142 | 
 | 
| 143 |     def testAssignFunctions(self):
 | 
| 144 |         arena = test_lib.MakeArena('')
 | 
| 145 | 
 | 
| 146 |         expr = 'ls; foo=42'
 | 
| 147 |         w_parser = test_lib.InitWordParser(expr, arena=arena)
 | 
| 148 | 
 | 
| 149 |         # Skip first two words
 | 
| 150 |         w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 151 |         w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 152 |         w3 = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 153 |         print(w3)
 | 
| 154 | 
 | 
| 155 |         self.assertEqual(False, lexer.IsPlusEquals(w3.parts[0]))
 | 
| 156 |         self.assertEqual('foo', lexer.TokenSliceRight(w3.parts[0], -1))
 | 
| 157 | 
 | 
| 158 |         expr = 'ls; foo+=X'
 | 
| 159 |         w_parser = test_lib.InitWordParser(expr, arena=arena)
 | 
| 160 | 
 | 
| 161 |         # Skip first two words
 | 
| 162 |         w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 163 |         w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 164 |         w3 = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 165 |         print(w3)
 | 
| 166 | 
 | 
| 167 |         self.assertEqual(True, lexer.IsPlusEquals(w3.parts[0]))
 | 
| 168 |         self.assertEqual('foo', lexer.TokenSliceRight(w3.parts[0], -2))
 | 
| 169 | 
 | 
| 170 | 
 | 
| 171 | class WordParserTest(unittest.TestCase):
 | 
| 172 | 
 | 
| 173 |     def testStaticEvalWord(self):
 | 
| 174 |         expr = r'\EOF'  # Quoted here doc delimiter
 | 
| 175 |         w_parser = test_lib.InitWordParser(expr)
 | 
| 176 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 177 |         ok, s, quoted = word_.StaticEval(w)
 | 
| 178 |         self.assertEqual(True, ok)
 | 
| 179 |         self.assertEqual('EOF', s)
 | 
| 180 |         self.assertEqual(True, quoted)
 | 
| 181 | 
 | 
| 182 |     def testDisambiguatePrefix(self):
 | 
| 183 |         w = _assertReadWord(self, '${#}')
 | 
| 184 |         self.assertEqual('#', _GetVarSub(self, w))
 | 
| 185 |         w = _assertReadWord(self, '${!}')
 | 
| 186 |         self.assertEqual('!', _GetVarSub(self, w))
 | 
| 187 |         w = _assertReadWord(self, '${?}')
 | 
| 188 |         self.assertEqual('?', _GetVarSub(self, w))
 | 
| 189 | 
 | 
| 190 |         w = _assertReadWord(self, '${var}')
 | 
| 191 | 
 | 
| 192 |         w = _assertReadWord(self, '${15}')
 | 
| 193 | 
 | 
| 194 |         w = _assertReadWord(self, '${#var}')
 | 
| 195 |         self.assertEqual(Id.VSub_Pound, _GetPrefixOp(self, w))
 | 
| 196 |         w = _assertReadWord(self, '${!ref}')
 | 
| 197 |         self.assertEqual(Id.VSub_Bang, _GetPrefixOp(self, w))
 | 
| 198 | 
 | 
| 199 |         # Length of length
 | 
| 200 |         w = _assertReadWord(self, '${##}')
 | 
| 201 |         self.assertEqual('#', _GetVarSub(self, w))
 | 
| 202 |         self.assertEqual(Id.VSub_Pound, _GetPrefixOp(self, w))
 | 
| 203 | 
 | 
| 204 |         w = _assertReadWord(self, '${array[0]}')
 | 
| 205 |         self.assertEqual(1, len(w.parts))
 | 
| 206 |         w = _assertReadWord(self, '${array[@]}')
 | 
| 207 |         self.assertEqual(1, len(w.parts))
 | 
| 208 | 
 | 
| 209 |         # Length of element
 | 
| 210 |         w = _assertReadWord(self, '${#array[0]}')
 | 
| 211 |         self.assertEqual(1, len(w.parts))
 | 
| 212 |         self.assertEqual(Id.VSub_Pound, _GetPrefixOp(self, w))
 | 
| 213 |         # Ref for element
 | 
| 214 |         w = _assertReadWord(self, '${!array[0]}')
 | 
| 215 |         self.assertEqual(1, len(w.parts))
 | 
| 216 |         self.assertEqual(Id.VSub_Bang, _GetPrefixOp(self, w))
 | 
| 217 | 
 | 
| 218 |         w = _assertReadWord(self, '${var#prefix}')
 | 
| 219 |         self.assertEqual(1, len(w.parts))
 | 
| 220 |         self.assertEqual(Id.VOp1_Pound, _GetSuffixOp(self, w).op.id)
 | 
| 221 | 
 | 
| 222 |         w = _assertReadWord(self, '${!var#prefix}')
 | 
| 223 |         self.assertEqual(1, len(w.parts))
 | 
| 224 |         self.assertEqual(Id.VSub_Bang, _GetPrefixOp(self, w))
 | 
| 225 |         self.assertEqual(Id.VOp1_Pound, _GetSuffixOp(self, w).op.id)
 | 
| 226 | 
 | 
| 227 |         _assertReadWordFailure(self, '${#var#prefix}')
 | 
| 228 | 
 | 
| 229 |         # Allowed by bash, but we don't parse it.  Use len=$#; echo ${len#2}
 | 
| 230 |         # instead.
 | 
| 231 |         _assertReadWordFailure(self, '${##2}')
 | 
| 232 | 
 | 
| 233 |     def testIncompleteWords(self):
 | 
| 234 |         # Bugs found in completion
 | 
| 235 |         w = _assertReadWordFailure(self, '${undef:-')
 | 
| 236 |         w = _assertReadWordFailure(self, '${undef:-$')
 | 
| 237 |         w = _assertReadWordFailure(self, '${undef:-$F')
 | 
| 238 | 
 | 
| 239 |         w = _assertReadWordFailure(self, '${x@')
 | 
| 240 |         w = _assertReadWordFailure(self, '${x@Q')
 | 
| 241 | 
 | 
| 242 |         w = _assertReadWordFailure(self, '${x%')
 | 
| 243 | 
 | 
| 244 |         w = _assertReadWordFailure(self, '${x/')
 | 
| 245 |         w = _assertReadWordFailure(self, '${x/a/')
 | 
| 246 |         w = _assertReadWordFailure(self, '${x/a/b')
 | 
| 247 |         w = _assertReadWordFailure(self, '${x:')
 | 
| 248 | 
 | 
| 249 |     def testVarOf(self):
 | 
| 250 |         w = _assertReadWord(self, '${name}')
 | 
| 251 |         w = _assertReadWord(self, '${name[0]}')
 | 
| 252 | 
 | 
| 253 |         w = _assertReadWord(self, '${array[@]}')
 | 
| 254 | 
 | 
| 255 |         # Should be DISALLOWED!
 | 
| 256 |         #w = _assertReadWord(self, '${11[@]}')
 | 
| 257 | 
 | 
| 258 |     def assertUnquoted(self, expected, w):
 | 
| 259 |         ok, s, quoted = word_.StaticEval(w)
 | 
| 260 |         self.assertTrue(ok)
 | 
| 261 |         self.assertEqual(expected, s)
 | 
| 262 |         self.assertFalse(quoted)
 | 
| 263 | 
 | 
| 264 |     def testPatSub(self):
 | 
| 265 |         w = _assertReadWord(self, '${var/pat/replace}')
 | 
| 266 |         op = _GetSuffixOp(self, w)
 | 
| 267 |         self.assertUnquoted('pat', op.pat)
 | 
| 268 |         self.assertUnquoted('replace', op.replace)
 | 
| 269 |         self.assertEqual(Id.Undefined_Tok, op.replace_mode)
 | 
| 270 | 
 | 
| 271 |         w = _assertReadWord(self, '${var//pat/replace}')  # sub all
 | 
| 272 |         op = _GetSuffixOp(self, w)
 | 
| 273 |         self.assertUnquoted('pat', op.pat)
 | 
| 274 |         self.assertUnquoted('replace', op.replace)
 | 
| 275 |         self.assertEqual(Id.Lit_Slash, op.replace_mode,
 | 
| 276 |                          Id_str(op.replace_mode))
 | 
| 277 | 
 | 
| 278 |         w = _assertReadWord(self, '${var/%pat/replace}')  # prefix
 | 
| 279 |         op = _GetSuffixOp(self, w)
 | 
| 280 |         self.assertUnquoted('pat', op.pat)
 | 
| 281 |         self.assertUnquoted('replace', op.replace)
 | 
| 282 |         self.assertEqual(Id.Lit_Percent, op.replace_mode)
 | 
| 283 | 
 | 
| 284 |         w = _assertReadWord(self, '${var/#pat/replace}')  # suffix
 | 
| 285 |         op = _GetSuffixOp(self, w)
 | 
| 286 |         self.assertUnquoted('pat', op.pat)
 | 
| 287 |         self.assertUnquoted('replace', op.replace)
 | 
| 288 |         self.assertEqual(Id.Lit_Pound, op.replace_mode)
 | 
| 289 | 
 | 
| 290 |         w = _assertReadWord(self, '${var/pat}')  # no replacement
 | 
| 291 |         w = _assertReadWord(self, '${var//pat}')  # no replacement
 | 
| 292 |         op = _GetSuffixOp(self, w)
 | 
| 293 |         self.assertUnquoted('pat', op.pat)
 | 
| 294 |         self.assertEqual(rhs_word_e.Empty, op.replace.tag())
 | 
| 295 |         self.assertEqual(Id.Lit_Slash, op.replace_mode)
 | 
| 296 | 
 | 
| 297 |         # replace with slash
 | 
| 298 |         w = _assertReadWord(self, '${var/pat//}')
 | 
| 299 |         op = _GetSuffixOp(self, w)
 | 
| 300 |         self.assertUnquoted('pat', op.pat)
 | 
| 301 |         self.assertUnquoted('/', op.replace)
 | 
| 302 | 
 | 
| 303 |         # replace with two slashes unquoted
 | 
| 304 |         w = _assertReadWord(self, '${var/pat///}')
 | 
| 305 |         op = _GetSuffixOp(self, w)
 | 
| 306 |         self.assertUnquoted('pat', op.pat)
 | 
| 307 |         self.assertUnquoted('//', op.replace)
 | 
| 308 | 
 | 
| 309 |         # replace with two slashes quoted
 | 
| 310 |         w = _assertReadWord(self, '${var/pat/"//"}')
 | 
| 311 |         op = _GetSuffixOp(self, w)
 | 
| 312 |         self.assertUnquoted('pat', op.pat)
 | 
| 313 | 
 | 
| 314 |         ok, s, quoted = word_.StaticEval(op.replace)
 | 
| 315 |         self.assertTrue(ok)
 | 
| 316 |         self.assertEqual('//', s)
 | 
| 317 |         self.assertTrue(quoted)
 | 
| 318 | 
 | 
| 319 |         # Real example found in the wild!
 | 
| 320 |         # http://www.oilshell.org/blog/2016/11/07.html
 | 
| 321 | 
 | 
| 322 |         # 2023-05: copied into spec/var-op-patsub.test.sh
 | 
| 323 |         w = _assertReadWord(self, r'${var////\\/}')
 | 
| 324 |         op = _GetSuffixOp(self, w)
 | 
| 325 |         self.assertEqual(Id.Lit_Slash, op.replace_mode)
 | 
| 326 | 
 | 
| 327 |         self.assertUnquoted('/', op.pat)
 | 
| 328 | 
 | 
| 329 |         ok, s, quoted = word_.StaticEval(op.replace)
 | 
| 330 |         self.assertTrue(ok)
 | 
| 331 |         self.assertEqual(r'\/', s)
 | 
| 332 | 
 | 
| 333 |     def testSlice(self):
 | 
| 334 |         w = _assertReadWord(self, '${foo:0}')
 | 
| 335 |         # No length
 | 
| 336 |         self.assertEqual(None, _GetSuffixOp(self, w).length)
 | 
| 337 | 
 | 
| 338 |         w = _assertReadWord(self, '${foo:0:1}')
 | 
| 339 |         w = _assertReadWord(self, '${foo:1+2:2+3}')
 | 
| 340 | 
 | 
| 341 |         # This is allowed
 | 
| 342 |         w = _assertReadWord(self, '${foo::1}')
 | 
| 343 |         # No beginning
 | 
| 344 |         self.assertEqual(arith_expr_e.EmptyZero,
 | 
| 345 |                          _GetSuffixOp(self, w).begin.tag())
 | 
| 346 | 
 | 
| 347 |     def testLength(self):
 | 
| 348 |         # Synonym for $#, had a bug here
 | 
| 349 |         w = _assertReadWord(self, '${#@}')
 | 
| 350 |         self.assertTrue(Id.VSub_Pound, _GetPrefixOp(self, w))
 | 
| 351 | 
 | 
| 352 |         # Length of arg 11
 | 
| 353 |         w = _assertReadWord(self, '${#11}')
 | 
| 354 |         self.assertTrue(Id.VSub_Pound, _GetPrefixOp(self, w))
 | 
| 355 | 
 | 
| 356 |         w = _assertReadWord(self, '${#str}')
 | 
| 357 |         self.assertTrue(Id.VSub_Pound, _GetPrefixOp(self, w))
 | 
| 358 | 
 | 
| 359 |         w = _assertReadWord(self, '${#array[0]}')
 | 
| 360 |         # BUG!
 | 
| 361 |         #self.assertTrue(VS_POUND, _GetSuffixOp(self, w).id)
 | 
| 362 | 
 | 
| 363 |         w = _assertReadWord(self, '${#array["key"]}')
 | 
| 364 |         # BUG!
 | 
| 365 |         #self.assertTrue(Id.VSub_POUND, _GetSuffixOp(self, w).id)
 | 
| 366 | 
 | 
| 367 |     def testUnary(self):
 | 
| 368 |         w = _assertReadWord(self, '${var#}')
 | 
| 369 |         self.assertTrue(Id.VOp1_Pound, _GetSuffixOp(self, w).op.id)
 | 
| 370 |         w = _assertReadWord(self, '${var#prefix}')
 | 
| 371 |         self.assertTrue(Id.VOp1_Pound, _GetSuffixOp(self, w).op.id)
 | 
| 372 | 
 | 
| 373 |         w = _assertReadWord(self, '${var##}')
 | 
| 374 |         self.assertTrue(Id.VOp1_DPound, _GetSuffixOp(self, w).op.id)
 | 
| 375 |         w = _assertReadWord(self, '${var##prefix}')
 | 
| 376 |         self.assertTrue(Id.VOp1_DPound, _GetSuffixOp(self, w).op.id)
 | 
| 377 | 
 | 
| 378 |         w = _assertReadWord(self, '${var%suffix}')
 | 
| 379 |         w = _assertReadWord(self, '${var%%suffix}')
 | 
| 380 | 
 | 
| 381 |     def testArrayOp(self):
 | 
| 382 |         w = _assertReadWord(self, '${array[0]}')
 | 
| 383 |         w = _assertReadWord(self, '${array[5+5]}')
 | 
| 384 | 
 | 
| 385 |         w = _assertReadWord(self, '${array[@]}')
 | 
| 386 |         w = _assertReadWord(self, '${array[*]}')
 | 
| 387 | 
 | 
| 388 |     def testTestOp(self):
 | 
| 389 |         w = _assertReadWord(self, '${var:-default]}')
 | 
| 390 | 
 | 
| 391 |     def testTildeLike(self):
 | 
| 392 |         w = _assertReadWord(self, '~/git/oilshell/oil')
 | 
| 393 |         w = _assertReadWord(self, '~andy/git/oilshell/oil')
 | 
| 394 |         w = _assertReadWord(self, '~andy_c/git/oilshell/oil')
 | 
| 395 |         w = _assertReadWord(self, '~andy.c/git/oilshell/oil')
 | 
| 396 |         w = _assertReadWord(self, '~andy-c/git/oilshell/oil')
 | 
| 397 |         w = _assertReadWord(self, '~andy-c:git/oilshell/oil')
 | 
| 398 | 
 | 
| 399 |     def testRead(self):
 | 
| 400 |         CASES = [
 | 
| 401 |             'ls "foo"',
 | 
| 402 |             '$(( 1 + 2 ))',
 | 
| 403 |             '$(echo $(( 1 )) )',  # OLD BUG: arith sub within command sub
 | 
| 404 |             'echo ${#array[@]} b',  # Had a bug here
 | 
| 405 |             'echo $(( ${#array[@]} ))',  # Bug here
 | 
| 406 | 
 | 
| 407 |             # Had a bug: unary minus
 | 
| 408 |             #'${mounted_disk_regex:0:-1}',
 | 
| 409 |             'echo ${@%suffix}',  # had a bug here
 | 
| 410 |             '${@}',
 | 
| 411 |             'echo ${var,,}',
 | 
| 412 |             'echo ${var,,?}',
 | 
| 413 | 
 | 
| 414 |             # Line continuation tests
 | 
| 415 |             '${\\\nfoo}',  # VSub_1
 | 
| 416 |             '${foo\\\n}',  # VSub_2
 | 
| 417 |             '${foo#\\\nyo}',  # VS_ARG_UNQ
 | 
| 418 |             '"${foo#\\\nyo}"',  # VS_ARG_DQ
 | 
| 419 |         ]
 | 
| 420 |         for expr in CASES:
 | 
| 421 |             print('---')
 | 
| 422 |             print(expr)
 | 
| 423 |             print()
 | 
| 424 | 
 | 
| 425 |             w_parser = test_lib.InitWordParser(expr)
 | 
| 426 | 
 | 
| 427 |             while True:
 | 
| 428 |                 w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 429 |                 assert w is not None
 | 
| 430 | 
 | 
| 431 |                 fmt.PrettyPrint(w)
 | 
| 432 | 
 | 
| 433 |                 if word_.CommandId(w) == Id.Eof_Real:
 | 
| 434 |                     break
 | 
| 435 | 
 | 
| 436 |     def testOilSplice(self):
 | 
| 437 |         w = _assertReadWord(self, '@words', oil_at=True)
 | 
| 438 | 
 | 
| 439 |         # These are normal words
 | 
| 440 |         w = _assertReadWord(self, '.@words', oil_at=True)
 | 
| 441 |         w = _assertReadWord(self, '.@words.', oil_at=True)
 | 
| 442 | 
 | 
| 443 |         # Errors
 | 
| 444 |         _assertReadWordFailure(self, '@words[', oil_at=True)
 | 
| 445 |         _assertReadWordFailure(self, '@words.', oil_at=True)
 | 
| 446 | 
 | 
| 447 |     def testReadComment(self):
 | 
| 448 |         # Test that we get Id.Op_Newline
 | 
| 449 |         code = 'foo # comment\nbar #comment\n'
 | 
| 450 |         w_parser = test_lib.InitWordParser(code)
 | 
| 451 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 452 |         assert w
 | 
| 453 |         self.assertEqual('foo', lexer.LazyStr(w.parts[0]))
 | 
| 454 | 
 | 
| 455 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 456 |         assert w
 | 
| 457 |         self.assertEqual(Id.Op_Newline, w.id)
 | 
| 458 | 
 | 
| 459 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 460 |         assert w
 | 
| 461 |         self.assertEqual('bar', lexer.LazyStr(w.parts[0]))
 | 
| 462 | 
 | 
| 463 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 464 |         assert w
 | 
| 465 |         self.assertEqual(Id.Op_Newline, w.id)
 | 
| 466 | 
 | 
| 467 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 468 |         assert w
 | 
| 469 |         self.assertEqual(Id.Eof_Real, w.id)
 | 
| 470 | 
 | 
| 471 |     def testReadArithWord(self):
 | 
| 472 |         w = _assertReadWord(self, '$(( (1+2) ))')
 | 
| 473 |         child = w.parts[0].anode
 | 
| 474 |         self.assertEqual(arith_expr_e.Binary, child.tag())
 | 
| 475 | 
 | 
| 476 |         w = _assertReadWord(self, '$(( (1+2) ))')
 | 
| 477 |         child = w.parts[0].anode
 | 
| 478 |         self.assertEqual(arith_expr_e.Binary, child.tag())
 | 
| 479 | 
 | 
| 480 |     def testReadArith(self):
 | 
| 481 |         CASES = [
 | 
| 482 |             '1 + 2',
 | 
| 483 |             'a + b',
 | 
| 484 |             '$a * $b',
 | 
| 485 |             '${a} * ${b}',
 | 
| 486 |             '$(echo 1) * $(echo 2)',
 | 
| 487 |             '`echo 1` + 2',
 | 
| 488 |             '$((1 + 2)) * $((3 + 4))',
 | 
| 489 |             "'single quoted'",  # Allowed by oil but not bash
 | 
| 490 |             '"${a}" + "${b}"',  # Ditto
 | 
| 491 |             '$# + $$',
 | 
| 492 |             # This doesn't work but does in bash -- should be 15
 | 
| 493 |             #'$(( $(echo 1)$(echo 2) + 3 ))',
 | 
| 494 |             '$(( x[0] < 5 ))',
 | 
| 495 |             '$(( ++i ))',
 | 
| 496 |             '$(( i++ ))',
 | 
| 497 |             '$(( x -= 1))',
 | 
| 498 |             '$(( x |= 1))',
 | 
| 499 |             '$(( x[0] = 1 ))',
 | 
| 500 |             '$(( 1 | 0 ))',
 | 
| 501 |             '$((0x$size))',
 | 
| 502 |         ]
 | 
| 503 | 
 | 
| 504 |         for expr in CASES:
 | 
| 505 |             print('---')
 | 
| 506 |             print(expr)
 | 
| 507 |             print()
 | 
| 508 | 
 | 
| 509 |             w_parser = test_lib.InitWordParser(expr)
 | 
| 510 |             # Can we remove this initialization?
 | 
| 511 |             w_parser._SetNext(lex_mode_e.Arith)
 | 
| 512 | 
 | 
| 513 |             while True:
 | 
| 514 |                 w = w_parser.ReadArithWord()
 | 
| 515 |                 assert w is not None
 | 
| 516 |                 fmt.PrettyPrint(w)
 | 
| 517 |                 if word_.CommandId(w) in (Id.Eof_Real, Id.Unknown_Tok):
 | 
| 518 |                     break
 | 
| 519 | 
 | 
| 520 |     def testHereDoc(self):
 | 
| 521 |         w_parser = test_lib.InitWordParser("""\
 | 
| 522 | ls foo
 | 
| 523 | 
 | 
| 524 | # Multiple newlines and comments should be ignored
 | 
| 525 | 
 | 
| 526 | ls bar
 | 
| 527 | """)
 | 
| 528 | 
 | 
| 529 |         def assertWord(w, id_, val):
 | 
| 530 |             self.assertEqual(1, len(w.parts))
 | 
| 531 |             part = w.parts[0]
 | 
| 532 |             self.assertEqual(id_, part.id)
 | 
| 533 |             self.assertEqual(val, lexer.LazyStr(part))
 | 
| 534 | 
 | 
| 535 |         print('--MULTI')
 | 
| 536 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 537 |         assertWord(w, Id.Lit_Chars, 'ls')
 | 
| 538 | 
 | 
| 539 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 540 |         assertWord(w, Id.Lit_Chars, 'foo')
 | 
| 541 | 
 | 
| 542 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 543 |         self.assertEqual(word_e.Operator, w.tag())
 | 
| 544 |         self.assertEqual(Id.Op_Newline, w.id)
 | 
| 545 |         self.assertEqual(None, w.tval)
 | 
| 546 | 
 | 
| 547 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 548 |         assertWord(w, Id.Lit_Chars, 'ls')
 | 
| 549 | 
 | 
| 550 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 551 |         assertWord(w, Id.Lit_Chars, 'bar')
 | 
| 552 | 
 | 
| 553 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 554 |         self.assertEqual(word_e.Operator, w.tag())
 | 
| 555 |         self.assertEqual(Id.Op_Newline, w.id)
 | 
| 556 |         self.assertEqual(None, w.tval)
 | 
| 557 | 
 | 
| 558 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 559 |         self.assertEqual(word_e.Operator, w.tag())
 | 
| 560 |         self.assertEqual(Id.Eof_Real, w.id)
 | 
| 561 |         self.assertEqual('', lexer.LazyStr(w))
 | 
| 562 | 
 | 
| 563 |     def testUnicode(self):
 | 
| 564 |         words = 'z \xce\xbb \xe4\xb8\x89 \xf0\x9f\x98\x98'
 | 
| 565 | 
 | 
| 566 |         def _Part(w, i):
 | 
| 567 |             return lexer.LazyStr(w.parts[i])
 | 
| 568 | 
 | 
| 569 |         w_parser = test_lib.InitWordParser(words)
 | 
| 570 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 571 |         self.assertEqual('z', _Part(w, 0))
 | 
| 572 | 
 | 
| 573 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 574 |         self.assertEqual('\xce\xbb', _Part(w, 0))
 | 
| 575 | 
 | 
| 576 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 577 |         self.assertEqual('\xe4\xb8\x89', _Part(w, 0))
 | 
| 578 | 
 | 
| 579 |         w = w_parser.ReadWord(lex_mode_e.ShCommand)
 | 
| 580 |         self.assertEqual('\xf0\x9f\x98\x98', _Part(w, 0))
 | 
| 581 | 
 | 
| 582 |     def testParseErrorLocation(self):
 | 
| 583 |         w = _assertSpanForWord(self, 'a=(1 2 3)')
 | 
| 584 | 
 | 
| 585 |         w = _assertSpanForWord(self, 'foo')
 | 
| 586 | 
 | 
| 587 |         w = _assertSpanForWord(self, '\\$')
 | 
| 588 | 
 | 
| 589 |         w = _assertSpanForWord(self, "''")
 | 
| 590 | 
 | 
| 591 |         w = _assertSpanForWord(self, "'sq'")
 | 
| 592 | 
 | 
| 593 |         w = _assertSpanForWord(self, '""')
 | 
| 594 | 
 | 
| 595 |         w = _assertSpanForWord(self, '"dq"')
 | 
| 596 | 
 | 
| 597 |         w = _assertSpanForWord(self, '$(echo command sub)')
 | 
| 598 | 
 | 
| 599 |         w = _assertSpanForWord(self, '$(( 1 + 2 ))')
 | 
| 600 | 
 | 
| 601 |         w = _assertSpanForWord(self, '~user')
 | 
| 602 | 
 | 
| 603 |         w = _assertSpanForWord(self, '${var#}')
 | 
| 604 | 
 | 
| 605 | 
 | 
| 606 | if __name__ == '__main__':
 | 
| 607 |     unittest.main()
 |