import unittest from calc.lexer import tokenize, Token, LexError def kinds(src): return [t.kind for t in tokenize(src)] def tok(src): return [(t.kind, t.value) for t in tokenize(src)] class TestNumbers(unittest.TestCase): def test_integer(self): tokens = tokenize("42") self.assertEqual(len(tokens), 2) self.assertEqual(tokens[0], Token('NUMBER', 42)) self.assertEqual(tokens[1], Token('EOF', None)) self.assertIsInstance(tokens[0].value, int) def test_float_standard(self): tokens = tokenize("3.14") self.assertEqual(tokens[0], Token('NUMBER', 3.14)) self.assertIsInstance(tokens[0].value, float) def test_float_leading_dot(self): tokens = tokenize(".5") self.assertEqual(tokens[0], Token('NUMBER', 0.5)) self.assertIsInstance(tokens[0].value, float) def test_float_trailing_dot(self): tokens = tokenize("10.") self.assertEqual(tokens[0], Token('NUMBER', 10.0)) self.assertIsInstance(tokens[0].value, float) def test_zero(self): tokens = tokenize("0") self.assertEqual(tokens[0], Token('NUMBER', 0)) class TestOperatorsAndParens(unittest.TestCase): def test_plus(self): self.assertIn(Token('PLUS', '+'), tokenize("+")) def test_minus(self): self.assertIn(Token('MINUS', '-'), tokenize("-")) def test_star(self): self.assertIn(Token('STAR', '*'), tokenize("*")) def test_slash(self): self.assertIn(Token('SLASH', '/'), tokenize("/")) def test_lparen(self): self.assertIn(Token('LPAREN', '('), tokenize("(")) def test_rparen(self): self.assertIn(Token('RPAREN', ')'), tokenize(")")) def test_expression(self): self.assertEqual( kinds("1+2*3"), ['NUMBER', 'PLUS', 'NUMBER', 'STAR', 'NUMBER', 'EOF'] ) def test_complex_expression(self): self.assertEqual( kinds("3.5*(1-2)"), ['NUMBER', 'STAR', 'LPAREN', 'NUMBER', 'MINUS', 'NUMBER', 'RPAREN', 'EOF'] ) class TestWhitespaceAndErrors(unittest.TestCase): def test_whitespace_skipped(self): self.assertEqual( kinds(" 12 + 3 "), ['NUMBER', 'PLUS', 'NUMBER', 'EOF'] ) t = tokenize(" 12 + 3 ") self.assertEqual(t[0].value, 12) self.assertEqual(t[1].kind, 'PLUS') self.assertEqual(t[2].value, 3) def test_tab_skipped(self): self.assertEqual(kinds("1\t+\t2"), ['NUMBER', 'PLUS', 'NUMBER', 'EOF']) def test_at_raises_lexerror(self): with self.assertRaises(LexError): tokenize("1 @ 2") def test_dollar_raises_lexerror(self): with self.assertRaises(LexError): tokenize("$") def test_letter_raises_lexerror(self): with self.assertRaises(LexError): tokenize("x") def test_lexerror_message_has_char_and_pos(self): try: tokenize("1 @ 2") self.fail("Expected LexError") except LexError as e: msg = str(e) self.assertIn('@', msg) self.assertIn('2', msg) # position 2 def test_eof_always_last(self): tokens = tokenize("1+2") self.assertEqual(tokens[-1].kind, 'EOF') def test_empty_string(self): tokens = tokenize("") self.assertEqual(tokens, [Token('EOF', None)]) if __name__ == '__main__': unittest.main()