diff --git a/SLS_Tests/generate_tests/__main__.py b/SLS_Tests/generate_tests/__main__.py index ab356f9..7e165bd 100644 --- a/SLS_Tests/generate_tests/__main__.py +++ b/SLS_Tests/generate_tests/__main__.py @@ -4,6 +4,7 @@ Generates comprehensive test cases for all integer types and bases. """ from .integer_tests import generate_integer_literal_tests +from .float_tests import generate_float_literal_tests if __name__ == "__main__": # import json @@ -12,6 +13,7 @@ if __name__ == "__main__": # Generate tests tests = [] tests += generate_integer_literal_tests() + tests += generate_float_literal_tests() # Print summary print(f"Generated {len(tests)} test cases") diff --git a/SLS_Tests/generate_tests/float_tests.py b/SLS_Tests/generate_tests/float_tests.py new file mode 100644 index 0000000..bf70c0f --- /dev/null +++ b/SLS_Tests/generate_tests/float_tests.py @@ -0,0 +1,367 @@ +from typing import List, Dict, Any, Optional +from .utils import Token, Operation, StackItem, RuntimeError, TestCase, to_dict + +class FloatTestGenerator: + """Generate test cases for floating point literals.""" + + # Special float values + SPECIAL_VALUES = { + 'f32': { + 'min': -3.4028235e38, + 'max': 3.4028235e38, + 'min_positive': 1.1754944e-38, + 'epsilon': 1.1920929e-7, + }, + 'f64': { + 'min': -1.7976931348623157e308, + 'max': 1.7976931348623157e308, + 'min_positive': 2.2250738585072014e-308, + 'epsilon': 2.220446049250313e-16, + } + } + + def __init__(self): + self.tests = [] + + def add_test(self, name: str, code: str, tokens: List[Token], + operations: Optional[List[Operation]] = None, + stack_final: Optional[List[StackItem]] = None, + runtime_error: Optional[RuntimeError] = None): + """Add a test case.""" + test = TestCase( + name=name, + code=code, + tokens=[to_dict(t) for t in tokens], + operations=[to_dict(o) for o in operations] if operations else None, + stack_final=[to_dict(s) for s in stack_final] if stack_final else None, + runtime_error=to_dict(runtime_error) if runtime_error else None + ) + self.tests.append(to_dict(test)) + + def make_push_op(self, type_name: str, value: Any) -> Operation: + """Create a push operation.""" + return Operation(function="push", type=type_name, value=value) + + def make_stack_item(self, type_name: str, value: Any) -> StackItem: + """Create a stack item.""" + return StackItem(type=type_name, value=value) + + def make_error_token(self, message: str) -> Token: + """Create an error token.""" + return Token(type="error", value=message) + + def make_success_test(self, name: str, code: str, type_name: str, value: float): + """Create a successful test case.""" + token = Token(type=type_name, value=value) + op = self.make_push_op(type_name, value) + stack = self.make_stack_item(type_name, value) + self.add_test(name, code, [token], [op], [stack]) + + def make_error_test(self, name: str, code: str, error_msg: str): + """Create an error test case.""" + token = self.make_error_token(error_msg) + self.add_test(name, code, [token]) + + def generate_basic_tests(self): + """Generate basic test cases.""" + # Simple default floats (f64) + self.make_success_test("Float Default Simple", "3.14", "f64", 3.14) + self.make_success_test("Float Default Zero", "0.0", "f64", 0.0) + self.make_success_test("Float Default Negative", "-2.5", "f64", -2.5) + self.make_success_test("Float Default One", "1.0", "f64", 1.0) + + # Simple with type annotation + self.make_success_test("Float f32 Simple", "3.14:f32", "f32", 3.14) + self.make_success_test("Float f64 Simple", "2.718:f64", "f64", 2.718) + + def generate_format_tests(self): + """Generate tests for different float formats.""" + # Leading zeros + self.make_success_test("Float Default Leading Zeros", "00042.5", "f64", 42.5) + self.make_success_test("Float Default Leading Zero Decimal", "0.5", "f64", 0.5) + + # Trailing zeros + self.make_success_test("Float Default Trailing Zeros", "3.1400", "f64", 3.14) + + # No leading digit + self.make_success_test("Float Default No Leading Digit", ".5", "f64", 0.5) + self.make_success_test("Float Default No Leading Digit Negative", "-.25", "f64", -0.25) + + # No trailing digits + self.make_success_test("Float Default No Trailing Digits", "42.", "f64", 42.0) + self.make_success_test("Float Default No Trailing Digits Negative", "-7.", "f64", -7.0) + + # Scientific notation + self.make_success_test("Float Default Scientific Positive Exp", "1.5e10", "f64", 1.5e10) + self.make_success_test("Float Default Scientific Negative Exp", "2.5e-5", "f64", 2.5e-5) + self.make_success_test("Float Default Scientific Capital E", "3.14E8", "f64", 3.14e8) + self.make_success_test("Float Default Scientific Plus Sign", "1.0e+3", "f64", 1000.0) + + # Very small numbers + self.make_success_test("Float Default Very Small", "0.000001", "f64", 0.000001) + self.make_success_test("Float Default Scientific Very Small", "1.0e-20", "f64", 1.0e-20) + + # Very large numbers + self.make_success_test("Float Default Very Large", "1000000.0", "f64", 1000000.0) + self.make_success_test("Float Default Scientific Very Large", "1.0e20", "f64", 1.0e20) + + def generate_underscore_tests(self): + """Generate tests for underscores in floats.""" + # Underscores in integer part + self.make_success_test("Float Default Underscore Integer Part", + "1_000_000.5", "f64", 1000000.5) + + # Underscores in decimal part + self.make_success_test("Float Default Underscore Decimal Part", + "3.141_592_653", "f64", 3.141592653) + + # Underscores in both parts + self.make_success_test("Float Default Underscore Both Parts", + "1_234.567_89", "f64", 1234.56789) + + # Underscores in scientific notation + self.make_success_test("Float Default Underscore Scientific Mantissa", + "1_000.5e10", "f64", 1000.5e10) + self.make_success_test("Float Default Underscore Scientific Exponent", + "1.5e1_0", "f64", 1.5e10) + + # Trailing underscore + self.make_success_test("Float Default Underscore Trailing", "42.5_", "f64", 42.5) + + # Double underscore + self.make_success_test("Float Default Underscore Double", "4__2.5", "f64", 42.5) + + # With type annotation + self.make_success_test("Float f32 With Underscores", + "1_234.567_89:f32", "f32", 1234.56789) + + def generate_special_value_tests(self): + """Generate tests for special float values.""" + # Infinity + self.make_success_test("Float Default Positive Infinity", "inf", "f64", float('inf')) + self.make_success_test("Float Default Negative Infinity", "-inf", "f64", float('-inf')) + self.make_success_test("Float f32 Positive Infinity", "inf:f32", "f32", float('inf')) + self.make_success_test("Float f32 Negative Infinity", "-inf:f32", "f32", float('-inf')) + + # NaN + self.make_success_test("Float Default NaN", "nan", "f64", float('nan')) + self.make_success_test("Float f32 NaN", "nan:f32", "f32", float('nan')) + + # Note: NaN comparison is special - NaN != NaN, so these tests may need + # special handling in the test runner + + def generate_edge_case_tests(self, type_name: str): + """Generate edge case tests for a specific float type.""" + values = self.SPECIAL_VALUES[type_name] + + # Maximum value + self.make_success_test(f"Float {type_name} Max Value", + f"{values['max']}:{type_name}", type_name, values['max']) + + # Minimum value (most negative) + self.make_success_test(f"Float {type_name} Min Value", + f"{values['min']}:{type_name}", type_name, values['min']) + + # Smallest positive normalized value + self.make_success_test(f"Float {type_name} Min Positive", + f"{values['min_positive']}:{type_name}", + type_name, values['min_positive']) + + # Machine epsilon + self.make_success_test(f"Float {type_name} Epsilon", + f"{values['epsilon']}:{type_name}", + type_name, values['epsilon']) + + # Near zero + self.make_success_test(f"Float {type_name} Near Zero Positive", + f"1e-30:{type_name}", type_name, 1e-30) + self.make_success_test(f"Float {type_name} Near Zero Negative", + f"-1e-30:{type_name}", type_name, -1e-30) + + # Subnormal numbers + if type_name == 'f64': + self.make_success_test("Float f64 Subnormal", + "1e-320:f64", "f64", 1e-320) + elif type_name == 'f32': + self.make_success_test("Float f32 Subnormal", + "1e-40:f32", "f32", 1e-40) + + def generate_overflow_tests(self): + """Generate overflow tests.""" + # f32 overflow + self.make_error_test("Float f32 Overflow Positive", + "1e40:f32", + "Float overflow: value exceeds range for f32.") + self.make_error_test("Float f32 Overflow Negative", + "-1e40:f32", + "Float overflow: value exceeds range for f32.") + + # f64 overflow (extremely large values) + self.make_error_test("Float f64 Overflow Positive", + "1e310:f64", + "Float overflow: value exceeds range for f64.") + self.make_error_test("Float f64 Overflow Negative", + "-1e310:f64", + "Float overflow: value exceeds range for f64.") + + def generate_precision_tests(self): + """Generate tests for precision limits.""" + # f32 precision (~7 decimal digits) + self.make_success_test("Float f32 Precision Limit", + "1.2345678:f32", "f32", 1.2345678) + self.make_success_test("Float f32 High Precision", + "3.141592653589793:f32", "f32", 3.141592653589793) + + # f64 precision (~15 decimal digits) + self.make_success_test("Float f64 Precision Limit", + "1.234567890123456:f64", "f64", 1.234567890123456) + self.make_success_test("Float f64 High Precision", + "3.141592653589793238:f64", "f64", 3.141592653589793238) + + # Very close numbers + self.make_success_test("Float f64 Close Numbers 1", + "1.0000000000000001:f64", "f64", 1.0000000000000001) + self.make_success_test("Float f64 Close Numbers 2", + "1.0000000000000002:f64", "f64", 1.0000000000000002) + + def generate_error_tests(self): + """Generate error tests.""" + # Invalid formats + self.make_error_test("Float Invalid No Decimal Point", + "42", + "Not a valid float literal: missing decimal point.") + self.make_error_test("Float Invalid Multiple Decimal Points", + "3.14.159", + "Invalid float literal: multiple decimal points.") + self.make_error_test("Float Invalid Only Decimal Point", + ".", + "Invalid float literal: no digits before or after decimal point.") + self.make_error_test("Float Invalid Characters", + "3.1a4", + "Invalid float literal: unexpected 'a' in float.") + + # Invalid scientific notation + self.make_error_test("Float Invalid Scientific No Exponent", + "3.14e", + "Invalid float literal: missing exponent value.") + self.make_error_test("Float Invalid Scientific Double E", + "3.14e10e5", + "Invalid float literal: multiple exponent markers.") + self.make_error_test("Float Invalid Scientific Invalid Exponent", + "3.14eX", + "Invalid float literal: invalid exponent 'X'.") + + # Invalid underscores + self.make_error_test("Float Invalid Leading Underscore", + "_3.14", + "Invalid float literal: leading underscore.") + self.make_error_test("Float Invalid Underscore Before Decimal", + "3_.14", + "Invalid float literal: underscore before decimal point.") + self.make_error_test("Float Invalid Underscore After Decimal", + "3._14", + "Invalid float literal: underscore after decimal point.") + + # Invalid type annotations + self.make_error_test("Float Invalid Type Annotation", + "3.14:i32", + "Type mismatch: float literal cannot be annotated as integer type.") + self.make_error_test("Float Invalid Type Name", + "3.14:f16", + "Invalid type annotation: unknown type 'f16'.") + + # Comma separators not allowed + self.make_error_test("Float Invalid Comma Separator", + "1,234.56", + "Invalid float literal: unexpected ',' in float.") + + def generate_whitespace_tests(self): + """Generate tests with whitespace.""" + self.make_success_test("Float Default Leading Whitespace", + " 3.14", "f64", 3.14) + self.make_success_test("Float Default Trailing Whitespace", + "3.14 ", "f64", 3.14) + self.make_success_test("Float Default Both Whitespace", + " 3.14 ", "f64", 3.14) + self.make_success_test("Float f32 With Whitespace", + " 2.718:f32 ", "f32", 2.718) + + def generate_mathematical_constants_tests(self): + """Generate tests for common mathematical constants.""" + # Pi + self.make_success_test("Float Default Pi Approximate", + "3.141592653589793", "f64", 3.141592653589793) + self.make_success_test("Float f32 Pi Approximate", + "3.1415927:f32", "f32", 3.1415927) + + # Euler's number + self.make_success_test("Float Default Euler Approximate", + "2.718281828459045", "f64", 2.718281828459045) + self.make_success_test("Float f32 Euler Approximate", + "2.7182817:f32", "f32", 2.7182817) + + # Golden ratio + self.make_success_test("Float Default Golden Ratio", + "1.618033988749895", "f64", 1.618033988749895) + + # Square root of 2 + self.make_success_test("Float Default Sqrt2", + "1.4142135623730951", "f64", 1.4142135623730951) + + def generate_signed_zero_tests(self): + """Generate tests for signed zeros.""" + self.make_success_test("Float Default Positive Zero", "0.0", "f64", 0.0) + self.make_success_test("Float Default Negative Zero", "-0.0", "f64", -0.0) + self.make_success_test("Float f32 Positive Zero", "0.0:f32", "f32", 0.0) + self.make_success_test("Float f32 Negative Zero", "-0.0:f32", "f32", -0.0) + + # Note: In IEEE 754, +0.0 and -0.0 are distinct values but compare equal + + def generate_all_tests(self) -> List[Dict[str, Any]]: + """Generate all test cases.""" + # Basic tests + self.generate_basic_tests() + + # Format variations + self.generate_format_tests() + + # Underscores + self.generate_underscore_tests() + + # Special values (inf, nan) + self.generate_special_value_tests() + + # Edge cases for each type + for type_name in ['f32', 'f64']: + self.generate_edge_case_tests(type_name) + + # Overflow tests + self.generate_overflow_tests() + + # Precision tests + self.generate_precision_tests() + + # Error tests + self.generate_error_tests() + + # Whitespace tests + self.generate_whitespace_tests() + + # Mathematical constants + self.generate_mathematical_constants_tests() + + # Signed zeros + self.generate_signed_zero_tests() + + return self.tests + + +def generate_float_literal_tests() -> List[Dict[str, Any]]: + """ + Main function to generate all floating point literal test cases. + + Returns: + List of test case dictionaries ready for serialization + """ + generator = FloatTestGenerator() + return generator.generate_all_tests()