@@ -41,6 +41,11 @@ class IntLit(Token):
4141 value : int
4242
4343
44+ @dataclass (eq = True )
45+ class FloatLit (Token ):
46+ value : float
47+
48+
4449@dataclass (eq = True )
4550class StringLit (Token ):
4651 value : str
@@ -174,7 +179,7 @@ def read_one(self) -> Token:
174179 return self .read_bytes ()
175180 raise ParseError (f"unexpected token { c !r} " )
176181 if c .isdigit ():
177- return self .read_integer (c )
182+ return self .read_number (c )
178183 if c in "()[]{}" :
179184 custom = {
180185 "(" : LeftParen ,
@@ -205,11 +210,23 @@ def read_comment(self) -> None:
205210 while self .has_input () and self .read_char () != "\n " :
206211 pass
207212
208- def read_integer (self , first_digit : str ) -> Token :
213+ def read_number (self , first_digit : str ) -> Token :
214+ # TODO: Support floating point numbers with no integer part
209215 buf = first_digit
210- while self .has_input () and (c := self .peek_char ()).isdigit ():
216+ has_decimal = False
217+ while self .has_input ():
218+ c = self .peek_char ()
219+ if c == "." :
220+ if has_decimal :
221+ raise ParseError (f"unexpected token { c !r} " )
222+ has_decimal = True
223+ elif not c .isdigit ():
224+ break
211225 self .read_char ()
212226 buf += c
227+
228+ if has_decimal :
229+ return self .make_token (FloatLit , float (buf ))
213230 return self .make_token (IntLit , int (buf ))
214231
215232 def _starts_operator (self , buf : str ) -> bool :
@@ -350,8 +367,9 @@ def parse(tokens: typing.List[Token], p: float = 0) -> "Object":
350367 token = tokens .pop (0 )
351368 l : Object
352369 if isinstance (token , IntLit ):
353- # TODO: Handle float literals
354370 l = Int (token .value )
371+ elif isinstance (token , FloatLit ):
372+ l = Float (token .value )
355373 elif isinstance (token , Name ):
356374 # TODO: Handle kebab case vars
357375 l = Var (token .value )
@@ -554,6 +572,21 @@ def __str__(self) -> str:
554572 return str (self .value )
555573
556574
575+ @dataclass (eq = True , frozen = True , unsafe_hash = True )
576+ class Float (Object ):
577+ value : float
578+
579+ def serialize (self ) -> Dict [bytes , object ]:
580+ raise NotImplementedError ("serialization for Float is not supported" )
581+
582+ @staticmethod
583+ def deserialize (msg : Dict [str , object ]) -> "Float" :
584+ raise NotImplementedError ("serialization for Float is not supported" )
585+
586+ def __str__ (self ) -> str :
587+ return str (self .value )
588+
589+
557590@dataclass (eq = True , frozen = True , unsafe_hash = True )
558591class String (Object ):
559592 value : str
@@ -971,15 +1004,15 @@ def __str__(self) -> str:
9711004 return f"#{ self .value } "
9721005
9731006
974- def unpack_int (obj : Object ) -> int :
975- if not isinstance (obj , Int ):
976- raise TypeError (f"expected Int, got { type (obj ).__name__ } " )
1007+ def unpack_number (obj : Object ) -> Union [ int , float ] :
1008+ if not isinstance (obj , ( Int , Float ) ):
1009+ raise TypeError (f"expected Int or Float , got { type (obj ).__name__ } " )
9771010 return obj .value
9781011
9791012
980- def eval_int (env : Env , exp : Object ) -> int :
1013+ def eval_number (env : Env , exp : Object ) -> Union [ int , float ] :
9811014 result = eval_exp (env , exp )
982- return unpack_int (result )
1015+ return unpack_number (result )
9831016
9841017
9851018def eval_str (env : Env , exp : Object ) -> str :
@@ -1007,20 +1040,30 @@ def make_bool(x: bool) -> Object:
10071040 return Symbol ("true" if x else "false" )
10081041
10091042
1043+ def wrap_inferred_number_type (x : Union [int , float ]) -> Object :
1044+ # TODO: Since this is intended to be a reference implementation
1045+ # we should avoid relying heavily on Python's implementation of
1046+ # arithmetic operations, type inference, and multiple dispatch.
1047+ # Update this to make the interpreter more language agnostic.
1048+ if isinstance (x , int ):
1049+ return Int (x )
1050+ return Float (x )
1051+
1052+
10101053BINOP_HANDLERS : Dict [BinopKind , Callable [[Env , Object , Object ], Object ]] = {
1011- BinopKind .ADD : lambda env , x , y : Int ( eval_int (env , x ) + eval_int (env , y )),
1012- BinopKind .SUB : lambda env , x , y : Int ( eval_int (env , x ) - eval_int (env , y )),
1013- BinopKind .MUL : lambda env , x , y : Int ( eval_int (env , x ) * eval_int (env , y )),
1014- BinopKind .DIV : lambda env , x , y : Int ( eval_int (env , x ) // eval_int (env , y )),
1015- BinopKind .FLOOR_DIV : lambda env , x , y : Int ( eval_int (env , x ) // eval_int (env , y )),
1016- BinopKind .EXP : lambda env , x , y : Int ( eval_int (env , x ) ** eval_int (env , y )),
1017- BinopKind .MOD : lambda env , x , y : Int ( eval_int (env , x ) % eval_int (env , y )),
1054+ BinopKind .ADD : lambda env , x , y : wrap_inferred_number_type ( eval_number (env , x ) + eval_number (env , y )),
1055+ BinopKind .SUB : lambda env , x , y : wrap_inferred_number_type ( eval_number (env , x ) - eval_number (env , y )),
1056+ BinopKind .MUL : lambda env , x , y : wrap_inferred_number_type ( eval_number (env , x ) * eval_number (env , y )),
1057+ BinopKind .DIV : lambda env , x , y : wrap_inferred_number_type ( eval_number (env , x ) / eval_number (env , y )),
1058+ BinopKind .FLOOR_DIV : lambda env , x , y : wrap_inferred_number_type ( eval_number (env , x ) // eval_number (env , y )),
1059+ BinopKind .EXP : lambda env , x , y : wrap_inferred_number_type ( eval_number (env , x ) ** eval_number (env , y )),
1060+ BinopKind .MOD : lambda env , x , y : wrap_inferred_number_type ( eval_number (env , x ) % eval_number (env , y )),
10181061 BinopKind .EQUAL : lambda env , x , y : make_bool (eval_exp (env , x ) == eval_exp (env , y )),
10191062 BinopKind .NOT_EQUAL : lambda env , x , y : make_bool (eval_exp (env , x ) != eval_exp (env , y )),
1020- BinopKind .LESS : lambda env , x , y : make_bool (eval_int (env , x ) < eval_int (env , y )),
1021- BinopKind .GREATER : lambda env , x , y : make_bool (eval_int (env , x ) > eval_int (env , y )),
1022- BinopKind .LESS_EQUAL : lambda env , x , y : make_bool (eval_int (env , x ) <= eval_int (env , y )),
1023- BinopKind .GREATER_EQUAL : lambda env , x , y : make_bool (eval_int (env , x ) >= eval_int (env , y )),
1063+ BinopKind .LESS : lambda env , x , y : make_bool (eval_number (env , x ) < eval_number (env , y )),
1064+ BinopKind .GREATER : lambda env , x , y : make_bool (eval_number (env , x ) > eval_number (env , y )),
1065+ BinopKind .LESS_EQUAL : lambda env , x , y : make_bool (eval_number (env , x ) <= eval_number (env , y )),
1066+ BinopKind .GREATER_EQUAL : lambda env , x , y : make_bool (eval_number (env , x ) >= eval_number (env , y )),
10241067 BinopKind .BOOL_AND : lambda env , x , y : make_bool (eval_bool (env , x ) and eval_bool (env , y )),
10251068 BinopKind .BOOL_OR : lambda env , x , y : make_bool (eval_bool (env , x ) or eval_bool (env , y )),
10261069 BinopKind .STRING_CONCAT : lambda env , x , y : String (eval_str (env , x ) + eval_str (env , y )),
@@ -1037,6 +1080,8 @@ class MatchError(Exception):
10371080def match (obj : Object , pattern : Object ) -> Optional [Env ]:
10381081 if isinstance (pattern , Int ):
10391082 return {} if isinstance (obj , Int ) and obj .value == pattern .value else None
1083+ if isinstance (pattern , Float ):
1084+ raise MatchError ("pattern matching is not supported for Floats" )
10401085 if isinstance (pattern , String ):
10411086 return {} if isinstance (obj , String ) and obj .value == pattern .value else None
10421087 if isinstance (pattern , Var ):
@@ -1092,7 +1137,7 @@ def match(obj: Object, pattern: Object) -> Optional[Env]:
10921137# pylint: disable=redefined-builtin
10931138def eval_exp (env : Env , exp : Object ) -> Object :
10941139 logger .debug (exp )
1095- if isinstance (exp , (Int , String , Bytes , Hole , Closure , NativeFunction , Symbol )):
1140+ if isinstance (exp , (Int , Float , String , Bytes , Hole , Closure , NativeFunction , Symbol )):
10961141 return exp
10971142 if isinstance (exp , Var ):
10981143 value = env .get (exp .name )
@@ -1285,6 +1330,23 @@ def test_tokenize_multiple_digits(self) -> None:
12851330 def test_tokenize_negative_int (self ) -> None :
12861331 self .assertEqual (tokenize ("-123" ), [Operator ("-" ), IntLit (123 )])
12871332
1333+ def test_tokenize_float (self ) -> None :
1334+ self .assertEqual (tokenize ("3.14" ), [FloatLit (3.14 )])
1335+
1336+ def test_tokenize_negative_float (self ) -> None :
1337+ self .assertEqual (tokenize ("-3.14" ), [Operator ("-" ), FloatLit (3.14 )])
1338+
1339+ @unittest .skip ("TODO: support floats with no integer part" )
1340+ def test_tokenize_float_with_no_integer_part (self ) -> None :
1341+ self .assertEqual (tokenize (".14" ), [FloatLit (0.14 )])
1342+
1343+ def test_tokenize_float_with_no_decimal_part (self ) -> None :
1344+ self .assertEqual (tokenize ("10." ), [FloatLit (10.0 )])
1345+
1346+ def test_tokenize_float_with_multiple_decimal_points_raises_parse_error (self ) -> None :
1347+ with self .assertRaisesRegex (ParseError , re .escape ("unexpected token '.'" )):
1348+ tokenize ("1.0.1" )
1349+
12881350 def test_tokenize_binop (self ) -> None :
12891351 self .assertEqual (tokenize ("1 + 2" ), [IntLit (1 ), Operator ("+" ), IntLit (2 )])
12901352
@@ -1708,6 +1770,12 @@ def test_parse_negative_int_binds_tighter_than_apply(self) -> None:
17081770 Apply (Binop (BinopKind .SUB , Int (0 ), Var ("l" )), Var ("r" )),
17091771 )
17101772
1773+ def test_parse_decimal_returns_float (self ) -> None :
1774+ self .assertEqual (parse ([FloatLit (3.14 )]), Float (3.14 ))
1775+
1776+ def test_parse_negative_float_returns_binary_sub_float (self ) -> None :
1777+ self .assertEqual (parse ([Operator ("-" ), FloatLit (3.14 )]), Binop (BinopKind .SUB , Int (0 ), Float (3.14 )))
1778+
17111779 def test_parse_var_returns_var (self ) -> None :
17121780 self .assertEqual (parse ([Name ("abc_123" )]), Var ("abc_123" ))
17131781
@@ -2122,6 +2190,18 @@ def test_match_with_inequal_ints_returns_none(self) -> None:
21222190 def test_match_int_with_non_int_returns_none (self ) -> None :
21232191 self .assertEqual (match (String ("abc" ), pattern = Int (1 )), None )
21242192
2193+ def test_match_with_equal_floats_raises_match_error (self ) -> None :
2194+ with self .assertRaisesRegex (MatchError , re .escape ("pattern matching is not supported for Floats" )):
2195+ match (Float (1 ), pattern = Float (1 ))
2196+
2197+ def test_match_with_inequal_floats_raises_match_error (self ) -> None :
2198+ with self .assertRaisesRegex (MatchError , re .escape ("pattern matching is not supported for Floats" )):
2199+ match (Float (2 ), pattern = Float (1 ))
2200+
2201+ def test_match_float_with_non_float_raises_match_error (self ) -> None :
2202+ with self .assertRaisesRegex (MatchError , re .escape ("pattern matching is not supported for Floats" )):
2203+ match (String ("abc" ), pattern = Float (1 ))
2204+
21252205 def test_match_with_equal_strings_returns_empty_dict (self ) -> None :
21262206 self .assertEqual (match (String ("a" ), pattern = String ("a" )), {})
21272207
@@ -2379,6 +2459,10 @@ def test_eval_int_returns_int(self) -> None:
23792459 exp = Int (5 )
23802460 self .assertEqual (eval_exp ({}, exp ), Int (5 ))
23812461
2462+ def test_eval_float_returns_float (self ) -> None :
2463+ exp = Float (3.14 )
2464+ self .assertEqual (eval_exp ({}, exp ), Float (3.14 ))
2465+
23822466 def test_eval_str_returns_str (self ) -> None :
23832467 exp = String ("xyz" )
23842468 self .assertEqual (eval_exp ({}, exp ), String ("xyz" ))
@@ -2410,7 +2494,7 @@ def test_eval_with_binop_add_with_int_string_raises_type_error(self) -> None:
24102494 exp = Binop (BinopKind .ADD , Int (1 ), String ("hello" ))
24112495 with self .assertRaises (TypeError ) as ctx :
24122496 eval_exp ({}, exp )
2413- self .assertEqual (ctx .exception .args [0 ], "expected Int, got String" )
2497+ self .assertEqual (ctx .exception .args [0 ], "expected Int or Float , got String" )
24142498
24152499 def test_eval_with_binop_sub (self ) -> None :
24162500 exp = Binop (BinopKind .SUB , Int (1 ), Int (2 ))
@@ -2421,8 +2505,8 @@ def test_eval_with_binop_mul(self) -> None:
24212505 self .assertEqual (eval_exp ({}, exp ), Int (6 ))
24222506
24232507 def test_eval_with_binop_div (self ) -> None :
2424- exp = Binop (BinopKind .DIV , Int (2 ), Int (3 ))
2425- self .assertEqual (eval_exp ({}, exp ), Int ( 0 ))
2508+ exp = Binop (BinopKind .DIV , Int (3 ), Int (10 ))
2509+ self .assertEqual (eval_exp ({}, exp ), Float ( 0.3 ))
24262510
24272511 def test_eval_with_binop_floor_div (self ) -> None :
24282512 exp = Binop (BinopKind .FLOOR_DIV , Int (2 ), Int (3 ))
@@ -2707,7 +2791,7 @@ def test_eval_less_returns_bool(self) -> None:
27072791
27082792 def test_eval_less_on_non_bool_raises_type_error (self ) -> None :
27092793 ast = Binop (BinopKind .LESS , String ("xyz" ), Int (4 ))
2710- with self .assertRaisesRegex (TypeError , re .escape ("expected Int, got String" )):
2794+ with self .assertRaisesRegex (TypeError , re .escape ("expected Int or Float , got String" )):
27112795 eval_exp ({}, ast )
27122796
27132797 def test_eval_less_equal_returns_bool (self ) -> None :
@@ -2716,7 +2800,7 @@ def test_eval_less_equal_returns_bool(self) -> None:
27162800
27172801 def test_eval_less_equal_on_non_bool_raises_type_error (self ) -> None :
27182802 ast = Binop (BinopKind .LESS_EQUAL , String ("xyz" ), Int (4 ))
2719- with self .assertRaisesRegex (TypeError , re .escape ("expected Int, got String" )):
2803+ with self .assertRaisesRegex (TypeError , re .escape ("expected Int or Float , got String" )):
27202804 eval_exp ({}, ast )
27212805
27222806 def test_eval_greater_returns_bool (self ) -> None :
@@ -2725,7 +2809,7 @@ def test_eval_greater_returns_bool(self) -> None:
27252809
27262810 def test_eval_greater_on_non_bool_raises_type_error (self ) -> None :
27272811 ast = Binop (BinopKind .GREATER , String ("xyz" ), Int (4 ))
2728- with self .assertRaisesRegex (TypeError , re .escape ("expected Int, got String" )):
2812+ with self .assertRaisesRegex (TypeError , re .escape ("expected Int or Float , got String" )):
27292813 eval_exp ({}, ast )
27302814
27312815 def test_eval_greater_equal_returns_bool (self ) -> None :
@@ -2734,7 +2818,7 @@ def test_eval_greater_equal_returns_bool(self) -> None:
27342818
27352819 def test_eval_greater_equal_on_non_bool_raises_type_error (self ) -> None :
27362820 ast = Binop (BinopKind .GREATER_EQUAL , String ("xyz" ), Int (4 ))
2737- with self .assertRaisesRegex (TypeError , re .escape ("expected Int, got String" )):
2821+ with self .assertRaisesRegex (TypeError , re .escape ("expected Int or Float , got String" )):
27382822 eval_exp ({}, ast )
27392823
27402824 def test_boolean_and_evaluates_args (self ) -> None :
@@ -2791,6 +2875,21 @@ def test_eval_record_with_spread_fails(self) -> None:
27912875 def test_eval_symbol_returns_symbol (self ) -> None :
27922876 self .assertEqual (eval_exp ({}, Symbol ("abc" )), Symbol ("abc" ))
27932877
2878+ def test_eval_float_and_float_addition_returns_float (self ) -> None :
2879+ self .assertEqual (eval_exp ({}, Binop (BinopKind .ADD , Float (1.0 ), Float (2.0 ))), Float (3.0 ))
2880+
2881+ def test_eval_int_and_float_addition_returns_float (self ) -> None :
2882+ self .assertEqual (eval_exp ({}, Binop (BinopKind .ADD , Int (1 ), Float (2.0 ))), Float (3.0 ))
2883+
2884+ def test_eval_int_and_float_division_returns_float (self ) -> None :
2885+ self .assertEqual (eval_exp ({}, Binop (BinopKind .DIV , Int (1 ), Float (2.0 ))), Float (0.5 ))
2886+
2887+ def test_eval_float_and_int_division_returns_float (self ) -> None :
2888+ self .assertEqual (eval_exp ({}, Binop (BinopKind .DIV , Float (1.0 ), Int (2 ))), Float (0.5 ))
2889+
2890+ def test_eval_int_and_int_division_returns_float (self ) -> None :
2891+ self .assertEqual (eval_exp ({}, Binop (BinopKind .DIV , Int (1 ), Int (2 ))), Float (0.5 ))
2892+
27942893
27952894class EndToEndTestsBase (unittest .TestCase ):
27962895 def _run (self , text : str , env : Optional [Env ] = None ) -> Object :
@@ -2805,6 +2904,9 @@ class EndToEndTests(EndToEndTestsBase):
28052904 def test_int_returns_int (self ) -> None :
28062905 self .assertEqual (self ._run ("1" ), Int (1 ))
28072906
2907+ def test_float_returns_float (self ) -> None :
2908+ self .assertEqual (self ._run ("3.14" ), Float (3.14 ))
2909+
28082910 def test_bytes_returns_bytes (self ) -> None :
28092911 self .assertEqual (self ._run ("~~QUJD" ), Bytes (b"ABC" ))
28102912
@@ -3604,6 +3706,11 @@ def test_serialize_negative_int(self) -> None:
36043706 obj = Int (- 123 )
36053707 self .assertEqual (obj .serialize (), {b"type" : b"Int" , b"value" : - 123 })
36063708
3709+ def test_serialize_float_raises_not_implemented_error (self ) -> None :
3710+ obj = Float (3.14 )
3711+ with self .assertRaisesRegex (NotImplementedError , re .escape ("serialization for Float is not supported" )):
3712+ obj .serialize ()
3713+
36073714 def test_serialize_str (self ) -> None :
36083715 obj = String ("abc" )
36093716 self .assertEqual (obj .serialize (), {b"type" : b"String" , b"value" : b"abc" })
@@ -3816,6 +3923,10 @@ def test_pretty_print_int(self) -> None:
38163923 obj = Int (1 )
38173924 self .assertEqual (str (obj ), "1" )
38183925
3926+ def test_pretty_print_float (self ) -> None :
3927+ obj = Float (3.14 )
3928+ self .assertEqual (str (obj ), "3.14" )
3929+
38193930 def test_pretty_print_string (self ) -> None :
38203931 obj = String ("hello" )
38213932 self .assertEqual (str (obj ), '"hello"' )
0 commit comments