import unittest from calc.lexer import tokenize, Token, LexError def kinds(src): return [t.kind for t in tokenize(src)] def kv(src): return [(t.kind, t.value) for t in tokenize(src)] class TestNumbers(unittest.TestCase): def test_integer(self): toks = tokenize("42") self.assertEqual(toks, [Token('NUMBER', 42), Token('EOF', None)]) self.assertIsInstance(toks[0].value, int) def test_float_standard(self): toks = tokenize("3.14") self.assertEqual(toks, [Token('NUMBER', 3.14), Token('EOF', None)]) self.assertIsInstance(toks[0].value, float) def test_float_leading_dot(self): toks = tokenize(".5") self.assertAlmostEqual(toks[0].value, 0.5) self.assertIsInstance(toks[0].value, float) def test_float_trailing_dot(self): toks = tokenize("10.") self.assertAlmostEqual(toks[0].value, 10.0) self.assertIsInstance(toks[0].value, float) def test_zero(self): toks = tokenize("0") self.assertEqual(toks[0], Token('NUMBER', 0)) self.assertIsInstance(toks[0].value, int) class TestOperatorsAndParens(unittest.TestCase): def test_all_ops(self): self.assertEqual(kinds("+-*/()"), ['PLUS','MINUS','STAR','SLASH','LPAREN','RPAREN','EOF']) def test_expression(self): self.assertEqual(kinds("1+2*3"), ['NUMBER','PLUS','NUMBER','STAR','NUMBER','EOF']) def test_values_preserved(self): self.assertEqual([t.value for t in tokenize("1+2*3")], [1, '+', 2, '*', 3, None]) def test_parens(self): self.assertEqual(kinds("(1+2)"), ['LPAREN','NUMBER','PLUS','NUMBER','RPAREN','EOF']) class TestWhitespaceAndErrors(unittest.TestCase): def test_spaces_between(self): self.assertEqual(kinds(" 12 + 3 "), ['NUMBER','PLUS','NUMBER','EOF']) vals = [t.value for t in tokenize(" 12 + 3 ")] self.assertEqual(vals, [12, '+', 3, None]) def test_complex_expr(self): toks = tokenize("3.5*(1-2)") expected_kinds = ['NUMBER','STAR','LPAREN','NUMBER','MINUS','NUMBER','RPAREN','EOF'] self.assertEqual([t.kind for t in toks], expected_kinds) self.assertAlmostEqual(toks[0].value, 3.5) def test_tabs(self): self.assertEqual(kinds("1\t+\t2"), ['NUMBER','PLUS','NUMBER','EOF']) def test_at_raises(self): with self.assertRaises(LexError) as ctx: tokenize("1 @ 2") self.assertIn('@', str(ctx.exception)) def test_dollar_raises(self): with self.assertRaises(LexError): tokenize("$5") def test_letter_raises(self): with self.assertRaises(LexError) as ctx: tokenize("1 x 2") self.assertIn('x', str(ctx.exception)) def test_error_position_in_message(self): try: tokenize("1 @ 2") except LexError as e: self.assertIn('2', str(e)) # position 2 def test_malformed_float_raises_lex_error(self): with self.assertRaises(LexError): tokenize("..") def test_malformed_float_multiple_dots_raises_lex_error(self): with self.assertRaises(LexError): tokenize("1.2.3") def test_bare_dot_raises_lex_error(self): with self.assertRaises(LexError): tokenize(".") if __name__ == '__main__': unittest.main()