104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
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()
|