Coverage for tests/test_exprParserYacc.py: 9%

380 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-03-12 10:07 +0000

1# This file is part of daf_butler. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <https://www.gnu.org/licenses/>. 

27 

28"""Simple unit test for exprParser subpackage module. 

29""" 

30 

31import unittest 

32 

33import astropy.time 

34from lsst.daf.butler.registry.queries.expressions.parser import ParseError, ParserYacc, TreeVisitor, exprTree 

35from lsst.daf.butler.registry.queries.expressions.parser.parserYacc import _parseTimeString 

36 

37 

38class _Visitor(TreeVisitor): 

39 """Trivial implementation of TreeVisitor.""" 

40 

41 def visitNumericLiteral(self, value, node): 

42 return f"N({value})" 

43 

44 def visitStringLiteral(self, value, node): 

45 return f"S({value})" 

46 

47 def visitTimeLiteral(self, value, node): 

48 return f"T({value})" 

49 

50 def visitRangeLiteral(self, start, stop, stride, node): 

51 if stride is None: 

52 return f"R({start}..{stop})" 

53 else: 

54 return f"R({start}..{stop}:{stride})" 

55 

56 def visitIdentifier(self, name, node): 

57 return f"ID({name})" 

58 

59 def visitUnaryOp(self, operator, operand, node): 

60 return f"U({operator} {operand})" 

61 

62 def visitBinaryOp(self, operator, lhs, rhs, node): 

63 return f"B({lhs} {operator} {rhs})" 

64 

65 def visitIsIn(self, lhs, values, not_in, node): 

66 values = ", ".join([str(val) for val in values]) 

67 if not_in: 

68 return f"!IN({lhs} ({values}))" 

69 else: 

70 return f"IN({lhs} ({values}))" 

71 

72 def visitParens(self, expression, node): 

73 return f"P({expression})" 

74 

75 

76class ParserLexTestCase(unittest.TestCase): 

77 """A test case for ParserYacc""" 

78 

79 def setUp(self): 

80 pass 

81 

82 def tearDown(self): 

83 pass 

84 

85 def testInstantiate(self): 

86 """Tests for making ParserLex instances""" 

87 parser = ParserYacc() # noqa: F841 

88 

89 def testEmpty(self): 

90 """Tests for empty expression""" 

91 parser = ParserYacc() 

92 

93 # empty expression is allowed, returns None 

94 tree = parser.parse("") 

95 self.assertIsNone(tree) 

96 

97 def testParseLiteral(self): 

98 """Tests for literals (strings/numbers)""" 

99 parser = ParserYacc() 

100 

101 tree = parser.parse("1") 

102 self.assertIsInstance(tree, exprTree.NumericLiteral) 

103 self.assertEqual(tree.value, "1") 

104 

105 tree = parser.parse(".5e-2") 

106 self.assertIsInstance(tree, exprTree.NumericLiteral) 

107 self.assertEqual(tree.value, ".5e-2") 

108 

109 tree = parser.parse("'string'") 

110 self.assertIsInstance(tree, exprTree.StringLiteral) 

111 self.assertEqual(tree.value, "string") 

112 

113 tree = parser.parse("10..20") 

114 self.assertIsInstance(tree, exprTree.RangeLiteral) 

115 self.assertEqual(tree.start, 10) 

116 self.assertEqual(tree.stop, 20) 

117 self.assertEqual(tree.stride, None) 

118 

119 tree = parser.parse("-10 .. 10:5") 

120 self.assertIsInstance(tree, exprTree.RangeLiteral) 

121 self.assertEqual(tree.start, -10) 

122 self.assertEqual(tree.stop, 10) 

123 self.assertEqual(tree.stride, 5) 

124 

125 # more extensive tests of time parsing is below 

126 tree = parser.parse("T'51544.0'") 

127 self.assertIsInstance(tree, exprTree.TimeLiteral) 

128 self.assertEqual(tree.value, astropy.time.Time(51544.0, format="mjd", scale="tai")) 

129 

130 tree = parser.parse("T'2020-03-30T12:20:33'") 

131 self.assertIsInstance(tree, exprTree.TimeLiteral) 

132 self.assertEqual(tree.value, astropy.time.Time("2020-03-30T12:20:33", format="isot", scale="utc")) 

133 

134 def testParseIdentifiers(self): 

135 """Tests for identifiers""" 

136 parser = ParserYacc() 

137 

138 tree = parser.parse("a") 

139 self.assertIsInstance(tree, exprTree.Identifier) 

140 self.assertEqual(tree.name, "a") 

141 

142 tree = parser.parse("a.b") 

143 self.assertIsInstance(tree, exprTree.Identifier) 

144 self.assertEqual(tree.name, "a.b") 

145 

146 def testParseParens(self): 

147 """Tests for identifiers""" 

148 parser = ParserYacc() 

149 

150 tree = parser.parse("(a)") 

151 self.assertIsInstance(tree, exprTree.Parens) 

152 self.assertIsInstance(tree.expr, exprTree.Identifier) 

153 self.assertEqual(tree.expr.name, "a") 

154 

155 def testUnaryOps(self): 

156 """Tests for unary plus and minus""" 

157 parser = ParserYacc() 

158 

159 tree = parser.parse("+a") 

160 self.assertIsInstance(tree, exprTree.UnaryOp) 

161 self.assertEqual(tree.op, "+") 

162 self.assertIsInstance(tree.operand, exprTree.Identifier) 

163 self.assertEqual(tree.operand.name, "a") 

164 

165 tree = parser.parse("- x.y") 

166 self.assertIsInstance(tree, exprTree.UnaryOp) 

167 self.assertEqual(tree.op, "-") 

168 self.assertIsInstance(tree.operand, exprTree.Identifier) 

169 self.assertEqual(tree.operand.name, "x.y") 

170 

171 def testBinaryOps(self): 

172 """Tests for binary operators""" 

173 parser = ParserYacc() 

174 

175 tree = parser.parse("a + b") 

176 self.assertIsInstance(tree, exprTree.BinaryOp) 

177 self.assertEqual(tree.op, "+") 

178 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

179 self.assertIsInstance(tree.rhs, exprTree.Identifier) 

180 self.assertEqual(tree.lhs.name, "a") 

181 self.assertEqual(tree.rhs.name, "b") 

182 

183 tree = parser.parse("a - 2") 

184 self.assertIsInstance(tree, exprTree.BinaryOp) 

185 self.assertEqual(tree.op, "-") 

186 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

187 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

188 self.assertEqual(tree.lhs.name, "a") 

189 self.assertEqual(tree.rhs.value, "2") 

190 

191 tree = parser.parse("2 * 2") 

192 self.assertIsInstance(tree, exprTree.BinaryOp) 

193 self.assertEqual(tree.op, "*") 

194 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

195 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

196 self.assertEqual(tree.lhs.value, "2") 

197 self.assertEqual(tree.rhs.value, "2") 

198 

199 tree = parser.parse("1.e5/2") 

200 self.assertIsInstance(tree, exprTree.BinaryOp) 

201 self.assertEqual(tree.op, "/") 

202 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

203 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

204 self.assertEqual(tree.lhs.value, "1.e5") 

205 self.assertEqual(tree.rhs.value, "2") 

206 

207 tree = parser.parse("333%76") 

208 self.assertIsInstance(tree, exprTree.BinaryOp) 

209 self.assertEqual(tree.op, "%") 

210 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

211 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

212 self.assertEqual(tree.lhs.value, "333") 

213 self.assertEqual(tree.rhs.value, "76") 

214 

215 # tests for overlaps operator 

216 tree = parser.parse("region1 OVERLAPS region2") 

217 self.assertIsInstance(tree, exprTree.BinaryOp) 

218 self.assertEqual(tree.op, "OVERLAPS") 

219 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

220 self.assertIsInstance(tree.rhs, exprTree.Identifier) 

221 

222 # time ranges with literals 

223 tree = parser.parse("(T'2020-01-01', T'2020-01-02') overlaps (T'2020-01-01', T'2020-01-02')") 

224 self.assertIsInstance(tree, exprTree.BinaryOp) 

225 self.assertEqual(tree.op, "OVERLAPS") 

226 self.assertIsInstance(tree.lhs, exprTree.TupleNode) 

227 self.assertIsInstance(tree.rhs, exprTree.TupleNode) 

228 

229 # but syntax allows anything, it's visitor responsibility to decide 

230 # what are the right operands 

231 tree = parser.parse("x+y Overlaps function(x-y)") 

232 self.assertIsInstance(tree, exprTree.BinaryOp) 

233 self.assertEqual(tree.op, "OVERLAPS") 

234 self.assertIsInstance(tree.lhs, exprTree.BinaryOp) 

235 self.assertIsInstance(tree.rhs, exprTree.FunctionCall) 

236 

237 def testIsIn(self): 

238 """Tests for IN""" 

239 parser = ParserYacc() 

240 

241 tree = parser.parse("a in (1,2,'X')") 

242 self.assertIsInstance(tree, exprTree.IsIn) 

243 self.assertFalse(tree.not_in) 

244 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

245 self.assertEqual(tree.lhs.name, "a") 

246 self.assertIsInstance(tree.values, list) 

247 self.assertEqual(len(tree.values), 3) 

248 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral) 

249 self.assertEqual(tree.values[0].value, "1") 

250 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral) 

251 self.assertEqual(tree.values[1].value, "2") 

252 self.assertIsInstance(tree.values[2], exprTree.StringLiteral) 

253 self.assertEqual(tree.values[2].value, "X") 

254 

255 tree = parser.parse("10 not in (1000, 2000..3000:100)") 

256 self.assertIsInstance(tree, exprTree.IsIn) 

257 self.assertTrue(tree.not_in) 

258 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

259 self.assertEqual(tree.lhs.value, "10") 

260 self.assertIsInstance(tree.values, list) 

261 self.assertEqual(len(tree.values), 2) 

262 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral) 

263 self.assertEqual(tree.values[0].value, "1000") 

264 self.assertIsInstance(tree.values[1], exprTree.RangeLiteral) 

265 self.assertEqual(tree.values[1].start, 2000) 

266 self.assertEqual(tree.values[1].stop, 3000) 

267 self.assertEqual(tree.values[1].stride, 100) 

268 

269 tree = parser.parse("10 in (-1000, -2000)") 

270 self.assertIsInstance(tree, exprTree.IsIn) 

271 self.assertFalse(tree.not_in) 

272 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

273 self.assertEqual(tree.lhs.value, "10") 

274 self.assertIsInstance(tree.values, list) 

275 self.assertEqual(len(tree.values), 2) 

276 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral) 

277 self.assertEqual(tree.values[0].value, "-1000") 

278 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral) 

279 self.assertEqual(tree.values[1].value, "-2000") 

280 

281 # test for time contained in time range, all literals 

282 tree = parser.parse("T'2020-01-01' in (T'2020-01-01', T'2020-01-02')") 

283 self.assertIsInstance(tree, exprTree.IsIn) 

284 self.assertFalse(tree.not_in) 

285 self.assertIsInstance(tree.lhs, exprTree.TimeLiteral) 

286 self.assertEqual(len(tree.values), 2) 

287 self.assertIsInstance(tree.values[0], exprTree.TimeLiteral) 

288 self.assertIsInstance(tree.values[1], exprTree.TimeLiteral) 

289 

290 # test for time range contained in time range 

291 tree = parser.parse("(T'2020-01-01', t1) in (T'2020-01-01', t2)") 

292 self.assertIsInstance(tree, exprTree.IsIn) 

293 self.assertFalse(tree.not_in) 

294 self.assertIsInstance(tree.lhs, exprTree.TupleNode) 

295 self.assertEqual(len(tree.values), 2) 

296 self.assertIsInstance(tree.values[0], exprTree.TimeLiteral) 

297 self.assertIsInstance(tree.values[1], exprTree.Identifier) 

298 

299 # test for point in region (we don't have region syntax yet, use 

300 # identifier) 

301 tree = parser.parse("point(1, 2) in (region1)") 

302 self.assertIsInstance(tree, exprTree.IsIn) 

303 self.assertFalse(tree.not_in) 

304 self.assertIsInstance(tree.lhs, exprTree.PointNode) 

305 self.assertEqual(len(tree.values), 1) 

306 self.assertIsInstance(tree.values[0], exprTree.Identifier) 

307 

308 # parens on right hand side are required 

309 with self.assertRaises(ParseError): 

310 parser.parse("point(1, 2) in region1") 

311 

312 # and we don't support full expressions in RHS list 

313 with self.assertRaises(ParseError): 

314 parser.parse("point(1, 2) in (x + y)") 

315 

316 def testCompareOps(self): 

317 """Tests for comparison operators""" 

318 parser = ParserYacc() 

319 

320 for op in ("=", "!=", "<", "<=", ">", ">="): 

321 tree = parser.parse(f"a {op} 10") 

322 self.assertIsInstance(tree, exprTree.BinaryOp) 

323 self.assertEqual(tree.op, op) 

324 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

325 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

326 self.assertEqual(tree.lhs.name, "a") 

327 self.assertEqual(tree.rhs.value, "10") 

328 

329 def testBoolOps(self): 

330 """Tests for boolean operators""" 

331 parser = ParserYacc() 

332 

333 for op in ("OR", "AND"): 

334 tree = parser.parse(f"a {op} b") 

335 self.assertIsInstance(tree, exprTree.BinaryOp) 

336 self.assertEqual(tree.op, op) 

337 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

338 self.assertIsInstance(tree.rhs, exprTree.Identifier) 

339 self.assertEqual(tree.lhs.name, "a") 

340 self.assertEqual(tree.rhs.name, "b") 

341 

342 tree = parser.parse("NOT b") 

343 self.assertIsInstance(tree, exprTree.UnaryOp) 

344 self.assertEqual(tree.op, "NOT") 

345 self.assertIsInstance(tree.operand, exprTree.Identifier) 

346 self.assertEqual(tree.operand.name, "b") 

347 

348 def testFunctionCall(self): 

349 """Tests for function calls""" 

350 parser = ParserYacc() 

351 

352 tree = parser.parse("f()") 

353 self.assertIsInstance(tree, exprTree.FunctionCall) 

354 self.assertEqual(tree.name, "f") 

355 self.assertEqual(tree.args, []) 

356 

357 tree = parser.parse("f1(a)") 

358 self.assertIsInstance(tree, exprTree.FunctionCall) 

359 self.assertEqual(tree.name, "f1") 

360 self.assertEqual(len(tree.args), 1) 

361 self.assertIsInstance(tree.args[0], exprTree.Identifier) 

362 self.assertEqual(tree.args[0].name, "a") 

363 

364 tree = parser.parse("anything_goes('a', x+y, ((a AND b) or (C = D)), NOT T < 42., Z IN (1,2,3,4))") 

365 self.assertIsInstance(tree, exprTree.FunctionCall) 

366 self.assertEqual(tree.name, "anything_goes") 

367 self.assertEqual(len(tree.args), 5) 

368 self.assertIsInstance(tree.args[0], exprTree.StringLiteral) 

369 self.assertIsInstance(tree.args[1], exprTree.BinaryOp) 

370 self.assertIsInstance(tree.args[2], exprTree.Parens) 

371 self.assertIsInstance(tree.args[3], exprTree.UnaryOp) 

372 self.assertIsInstance(tree.args[4], exprTree.IsIn) 

373 

374 with self.assertRaises(ParseError): 

375 parser.parse("f.ff()") 

376 

377 def testPointNode(self): 

378 """Tests for POINT() function""" 

379 parser = ParserYacc() 

380 

381 # POINT function makes special node type 

382 tree = parser.parse("POINT(Object.ra, 0.0)") 

383 self.assertIsInstance(tree, exprTree.PointNode) 

384 self.assertIsInstance(tree.ra, exprTree.Identifier) 

385 self.assertEqual(tree.ra.name, "Object.ra") 

386 self.assertIsInstance(tree.dec, exprTree.NumericLiteral) 

387 self.assertEqual(tree.dec.value, "0.0") 

388 

389 # it is not case sensitive 

390 tree = parser.parse("Point(1, 1)") 

391 self.assertIsInstance(tree, exprTree.PointNode) 

392 

393 def testTupleNode(self): 

394 """Tests for tuple""" 

395 parser = ParserYacc() 

396 

397 # test with simple identifier and literal 

398 tree = parser.parse("(Object.ra, 0.0)") 

399 self.assertIsInstance(tree, exprTree.TupleNode) 

400 self.assertEqual(len(tree.items), 2) 

401 self.assertIsInstance(tree.items[0], exprTree.Identifier) 

402 self.assertEqual(tree.items[0].name, "Object.ra") 

403 self.assertIsInstance(tree.items[1], exprTree.NumericLiteral) 

404 self.assertEqual(tree.items[1].value, "0.0") 

405 

406 # any expression can appear in tuple 

407 tree = parser.parse("(x+y, ((a AND b) or (C = D)))") 

408 self.assertIsInstance(tree, exprTree.TupleNode) 

409 self.assertEqual(len(tree.items), 2) 

410 self.assertIsInstance(tree.items[0], exprTree.BinaryOp) 

411 self.assertIsInstance(tree.items[1], exprTree.Parens) 

412 

413 # only two items can appear in a tuple 

414 with self.assertRaises(ParseError): 

415 parser.parse("(1, 2, 3)") 

416 

417 def testExpression(self): 

418 """Test for more or less complete expression""" 

419 parser = ParserYacc() 

420 

421 expression = ( 

422 "((instrument='HSC' AND detector != 9) OR instrument='CFHT') " 

423 "AND tract=8766 AND patch.cell_x > 5 AND " 

424 "patch.cell_y < 4 AND band='i'" 

425 ) 

426 

427 tree = parser.parse(expression) 

428 self.assertIsInstance(tree, exprTree.BinaryOp) 

429 self.assertEqual(tree.op, "AND") 

430 self.assertIsInstance(tree.lhs, exprTree.BinaryOp) 

431 # AND is left-associative, so rhs operand will be the 

432 # last sub-expressions 

433 self.assertIsInstance(tree.rhs, exprTree.BinaryOp) 

434 self.assertEqual(tree.rhs.op, "=") 

435 self.assertIsInstance(tree.rhs.lhs, exprTree.Identifier) 

436 self.assertEqual(tree.rhs.lhs.name, "band") 

437 self.assertIsInstance(tree.rhs.rhs, exprTree.StringLiteral) 

438 self.assertEqual(tree.rhs.rhs.value, "i") 

439 

440 def testSubstitution(self): 

441 """Test for identifier substitution""" 

442 # substitution is not recursive, so we can swap id2/id3 

443 idMap = { 

444 "id1": exprTree.StringLiteral("id1 value"), 

445 "id2": exprTree.Identifier("id3"), 

446 "id3": exprTree.Identifier("id2"), 

447 "POINT": exprTree.StringLiteral("not used"), 

448 "OR": exprTree.StringLiteral("not used"), 

449 } 

450 parser = ParserYacc(idMap=idMap) 

451 

452 expression = "id1 = 'v'" 

453 tree = parser.parse(expression) 

454 self.assertIsInstance(tree, exprTree.BinaryOp) 

455 self.assertEqual(tree.op, "=") 

456 self.assertIsInstance(tree.lhs, exprTree.StringLiteral) 

457 self.assertEqual(tree.lhs.value, "id1 value") 

458 

459 expression = "id2 - id3" 

460 tree = parser.parse(expression) 

461 self.assertIsInstance(tree, exprTree.BinaryOp) 

462 self.assertEqual(tree.op, "-") 

463 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

464 self.assertEqual(tree.lhs.name, "id3") 

465 self.assertIsInstance(tree.rhs, exprTree.Identifier) 

466 self.assertEqual(tree.rhs.name, "id2") 

467 

468 # reserved words are not substituted 

469 expression = "id2 OR id3" 

470 tree = parser.parse(expression) 

471 self.assertIsInstance(tree, exprTree.BinaryOp) 

472 self.assertEqual(tree.op, "OR") 

473 

474 # function names are not substituted 

475 expression = "POINT(1, 2)" 

476 tree = parser.parse(expression) 

477 self.assertIsInstance(tree, exprTree.PointNode) 

478 

479 def testException(self): 

480 """Test for exceptional cases""" 

481 

482 def _assertExc(exc, expr, token, pos, lineno, posInLine): 

483 """Check exception attribute values""" 

484 self.assertEqual(exc.expression, expr) 

485 self.assertEqual(exc.token, token) 

486 self.assertEqual(exc.pos, pos) 

487 self.assertEqual(exc.lineno, lineno) 

488 self.assertEqual(exc.posInLine, posInLine) 

489 

490 parser = ParserYacc() 

491 

492 expression = "(1, 2, 3)" 

493 with self.assertRaises(ParseError) as catcher: 

494 parser.parse(expression) 

495 _assertExc(catcher.exception, expression, ",", 5, 1, 5) 

496 

497 expression = "\n(1\n,\n 2, 3)" 

498 with self.assertRaises(ParseError) as catcher: 

499 parser.parse(expression) 

500 _assertExc(catcher.exception, expression, ",", 8, 4, 2) 

501 

502 expression = "T'not-a-time'" 

503 with self.assertRaises(ParseError) as catcher: 

504 parser.parse(expression) 

505 _assertExc(catcher.exception, expression, "not-a-time", 0, 1, 0) 

506 

507 def testStr(self): 

508 """Test for formatting""" 

509 parser = ParserYacc() 

510 

511 tree = parser.parse("(a+b)") 

512 self.assertEqual(str(tree), "(a + b)") 

513 

514 tree = parser.parse("1 in (1,'x',3)") 

515 self.assertEqual(str(tree), "1 IN (1, 'x', 3)") 

516 

517 tree = parser.parse("a not in (1,'x',3)") 

518 self.assertEqual(str(tree), "a NOT IN (1, 'x', 3)") 

519 

520 tree = parser.parse("(A or B) And NoT (x+3 > y)") 

521 self.assertEqual(str(tree), "(A OR B) AND NOT (x + 3 > y)") 

522 

523 tree = parser.parse("A in (100, 200..300:50)") 

524 self.assertEqual(str(tree), "A IN (100, 200..300:50)") 

525 

526 def testVisit(self): 

527 """Test for visitor methods""" 

528 # test should cover all visit* methods 

529 parser = ParserYacc() 

530 visitor = _Visitor() 

531 

532 tree = parser.parse("(a+b)") 

533 result = tree.visit(visitor) 

534 self.assertEqual(result, "P(B(ID(a) + ID(b)))") 

535 

536 tree = parser.parse("(A or B) and not (x + 3 > y)") 

537 result = tree.visit(visitor) 

538 self.assertEqual(result, "B(P(B(ID(A) OR ID(B))) AND U(NOT P(B(B(ID(x) + N(3)) > ID(y)))))") 

539 

540 tree = parser.parse("x in (1,2) AND y NOT IN (1.1, .25, 1e2) OR z in ('a', 'b')") 

541 result = tree.visit(visitor) 

542 self.assertEqual( 

543 result, 

544 "B(B(IN(ID(x) (N(1), N(2))) AND !IN(ID(y) (N(1.1), N(.25), N(1e2)))) OR IN(ID(z) (S(a), S(b))))", 

545 ) 

546 

547 tree = parser.parse("x in (1,2,5..15) AND y NOT IN (-100..100:10)") 

548 result = tree.visit(visitor) 

549 self.assertEqual(result, "B(IN(ID(x) (N(1), N(2), R(5..15))) AND !IN(ID(y) (R(-100..100:10))))") 

550 

551 tree = parser.parse("time > T'2020-03-30'") 

552 result = tree.visit(visitor) 

553 self.assertEqual(result, "B(ID(time) > T(2020-03-30 00:00:00.000))") 

554 

555 def testParseTimeStr(self): 

556 """Test for _parseTimeString method""" 

557 # few expected failures 

558 bad_times = [ 

559 "", 

560 " ", 

561 "123.456e10", # no exponents 

562 "mjd-dj/123.456", # format can only have word chars 

563 "123.456/mai-tai", # scale can only have word chars 

564 "2020-03-01 00", # iso needs minutes if hour is given 

565 "2020-03-01T", # isot needs hour:minute 

566 "2020:100:12", # yday needs minutes if hour is given 

567 "format/123456.00", # unknown format 

568 "123456.00/unscale", # unknown scale 

569 ] 

570 for bad_time in bad_times: 

571 with self.assertRaises(ValueError): 

572 _parseTimeString(bad_time) 

573 

574 # each tuple is (string, value, format, scale) 

575 tests = [ 

576 ("51544.0", 51544.0, "mjd", "tai"), 

577 ("mjd/51544.0", 51544.0, "mjd", "tai"), 

578 ("51544.0/tai", 51544.0, "mjd", "tai"), 

579 ("mjd/51544.0/tai", 51544.0, "mjd", "tai"), 

580 ("MJd/51544.0/TAi", 51544.0, "mjd", "tai"), 

581 ("jd/2451544.5", 2451544.5, "jd", "tai"), 

582 ("jd/2451544.5", 2451544.5, "jd", "tai"), 

583 ("51544.0/utc", 51544.0, "mjd", "utc"), 

584 ("unix/946684800.0", 946684800.0, "unix", "utc"), 

585 ("cxcsec/63072064.184", 63072064.184, "cxcsec", "tt"), 

586 ("2020-03-30", "2020-03-30 00:00:00.000", "iso", "utc"), 

587 ("2020-03-30 12:20", "2020-03-30 12:20:00.000", "iso", "utc"), 

588 ("2020-03-30 12:20:33.456789", "2020-03-30 12:20:33.457", "iso", "utc"), 

589 ("2020-03-30T12:20", "2020-03-30T12:20:00.000", "isot", "utc"), 

590 ("2020-03-30T12:20:33.456789", "2020-03-30T12:20:33.457", "isot", "utc"), 

591 ("isot/2020-03-30", "2020-03-30T00:00:00.000", "isot", "utc"), 

592 ("2020-03-30/tai", "2020-03-30 00:00:00.000", "iso", "tai"), 

593 ("+02020-03-30", "2020-03-30T00:00:00.000", "fits", "utc"), 

594 ("+02020-03-30T12:20:33", "2020-03-30T12:20:33.000", "fits", "utc"), 

595 ("+02020-03-30T12:20:33.456789", "2020-03-30T12:20:33.457", "fits", "utc"), 

596 ("fits/2020-03-30", "2020-03-30T00:00:00.000", "fits", "utc"), 

597 ("2020:123", "2020:123:00:00:00.000", "yday", "utc"), 

598 ("2020:123:12:20", "2020:123:12:20:00.000", "yday", "utc"), 

599 ("2020:123:12:20:33.456789", "2020:123:12:20:33.457", "yday", "utc"), 

600 ("yday/2020:123:12:20/tai", "2020:123:12:20:00.000", "yday", "tai"), 

601 ] 

602 for time_str, value, fmt, scale in tests: 

603 time = _parseTimeString(time_str) 

604 self.assertEqual(time.value, value) 

605 self.assertEqual(time.format, fmt) 

606 self.assertEqual(time.scale, scale) 

607 

608 

609if __name__ == "__main__": 

610 unittest.main()