import unittest from calc.lexer import tokenize, Token, LexError class TestNumbers(unittest.TestCase): def test_integer(self): result = tokenize("42") self.assertEqual(result, [Token('NUMBER', 42), Token('EOF', None)]) self.assertIsInstance(result[0].value, int) def test_float_standard(self): result = tokenize("3.14") self.assertEqual(result[0].kind, 'NUMBER') self.assertAlmostEqual(result[0].value, 3.14) self.assertIsInstance(result[0].value, float) def test_float_leading_dot(self): result = tokenize(".5") self.assertEqual(result[0].kind, 'NUMBER') self.assertAlmostEqual(result[0].value, 0.5) self.assertIsInstance(result[0].value, float) def test_float_trailing_dot(self): result = tokenize("10.") self.assertEqual(result[0].kind, 'NUMBER') self.assertAlmostEqual(result[0].value, 10.0) self.assertIsInstance(result[0].value, float) def test_eof_is_last(self): result = tokenize("42") self.assertEqual(result[-1].kind, 'EOF') class TestOperatorsAndParens(unittest.TestCase): def _kinds(self, src): return [t.kind for t in tokenize(src)] def test_plus(self): self.assertEqual(self._kinds("+"), ['PLUS', 'EOF']) def test_minus(self): self.assertEqual(self._kinds("-"), ['MINUS', 'EOF']) def test_star(self): self.assertEqual(self._kinds("*"), ['STAR', 'EOF']) def test_slash(self): self.assertEqual(self._kinds("/"), ['SLASH', 'EOF']) def test_lparen(self): self.assertEqual(self._kinds("("), ['LPAREN', 'EOF']) def test_rparen(self): self.assertEqual(self._kinds(")"), ['RPAREN', 'EOF']) def test_expression_1_plus_2_star_3(self): self.assertEqual( self._kinds("1+2*3"), ['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF'], ) class TestWhitespaceAndErrors(unittest.TestCase): def _kinds(self, src): return [t.kind for t in tokenize(src)] def test_whitespace_around_tokens(self): result = tokenize(" 12 + 3 ") self.assertEqual( [t.kind for t in result], ['NUMBER', 'PLUS', 'NUMBER', 'EOF'], ) nums = [t.value for t in result if t.kind == 'NUMBER'] self.assertEqual(nums, [12, 3]) def test_complex_expression(self): result = tokenize("3.5*(1-2)") self.assertEqual( [t.kind for t in result], ['NUMBER', 'STAR', 'LPAREN', 'NUMBER', 'MINUS', 'NUMBER', 'RPAREN', 'EOF'], ) self.assertAlmostEqual(result[0].value, 3.5) self.assertEqual(result[3].value, 1) self.assertEqual(result[5].value, 2) def test_lex_error_at_sign(self): with self.assertRaises(LexError): tokenize("1 @ 2") def test_lex_error_dollar(self): with self.assertRaises(LexError): tokenize("$") def test_lex_error_letter(self): with self.assertRaises(LexError): tokenize("x + 1") def test_lex_error_message_contains_char(self): with self.assertRaises(LexError) as ctx: tokenize("1 @ 2") self.assertIn('@', str(ctx.exception)) def test_lex_error_message_contains_position(self): with self.assertRaises(LexError) as ctx: tokenize("1 @ 2") # '@' is at position 2 self.assertIn('2', str(ctx.exception)) def test_tab_whitespace(self): result = tokenize("1\t+\t2") self.assertEqual( [t.kind for t in result], ['NUMBER', 'PLUS', 'NUMBER', 'EOF'], ) if __name__ == '__main__': unittest.main()