diff --git a/AlgebraicDataflowArchitectureModel/src/code/ast/NumberLiteral.java b/AlgebraicDataflowArchitectureModel/src/code/ast/NumberLiteral.java index 952450c..be1dbd5 100644 --- a/AlgebraicDataflowArchitectureModel/src/code/ast/NumberLiteral.java +++ b/AlgebraicDataflowArchitectureModel/src/code/ast/NumberLiteral.java @@ -48,10 +48,9 @@ } // TODO: 0 check - // TODO: Binary number // TODO: Octal number - if (NumberUtil.isHexNumber(tokenValue)) { + if (NumberUtil.isBinaryNumber(tokenValue) || NumberUtil.isHexNumber(tokenValue)) { this.tokenValue = tokenValue; } else { throw new IllegalArgumentException("Invalid number literal: " + tokenValue); diff --git a/AlgebraicDataflowArchitectureModel/src/code/ast/NumberUtil.java b/AlgebraicDataflowArchitectureModel/src/code/ast/NumberUtil.java index c637f00..fee1c7a 100644 --- a/AlgebraicDataflowArchitectureModel/src/code/ast/NumberUtil.java +++ b/AlgebraicDataflowArchitectureModel/src/code/ast/NumberUtil.java @@ -7,6 +7,90 @@ */ public class NumberUtil { /** + * Determines whether the given string is a valid binary number. + * A valid binary number starts with "0b" or "0B", followed by a + * combination of digits (0 or 1) or underscores ('_'), + * with restrictions on the positioning of underscores. + * + * @param token The string to be evaluated as a binary number. + * @return {@code true} if the given string represents a valid binary number, + * {@code false} otherwise. + * @apiNote This method returns {@code true} if the given token is a valid binary number representation in Java source code. + * Therefore, the validation rules strictly adhere to the Java language specification. + */ + public static boolean isBinaryNumber(String token) { + if (token == null) { + return false; + } + + int length = token.length(); + if (length < 3) { + return false; + } + + Lexer lexer = new Lexer(token.toLowerCase()); + + if (lexer.peek() != '0' || lexer.peekNext() != 'b') { + return false; + } + + lexer.advance(); // 0 + lexer.advance(); // b + + // Check for cases like 0b or 0b_ (Invalid underscore position) + if (lexer.peek() == '\0' || lexer.peek() == '_') { + return false; + } + + char prev = 'b'; // Initialize the previous character as 'b' + boolean hasDigit = false; + + while (lexer.peek() != '\0') { + char c = lexer.advance(); + + if (c == 'l') { + // The suffix for a long literal must be the last character in the literal. + if (lexer.peek() != '\0') { + return false; + } + + // The suffix must NOT be placed immediately after an underscore + if (prev == '_') { + return false; + } + + // Valid suffix found; finish checking + break; + } + + boolean isDigit = c == '0' || c == '1'; + boolean isValidFormat = isDigit || (c == '_'); + + if (!isValidFormat) { + return false; + } + + if (isDigit) { + hasDigit = true; + } + + // Save the previous character + prev = c; + } + + // Check for cases like 0b0101_ (Invalid trailing underscore) + if (prev == '_') { + return false; + } + + if (!hasDigit) { + return false; + } + + return true; + } + + /** * Determines whether the given string is a valid hexadecimal number. * A valid hexadecimal number starts with "0x" or "0X", followed by a * combination of digits (0-9), letters (a-f, A-F), or underscores ('_'), diff --git a/AlgebraicDataflowArchitectureModel/src/tests/NumberUtilTest.java b/AlgebraicDataflowArchitectureModel/src/tests/NumberUtilTest.java index 31c7f7e..9c47aee 100644 --- a/AlgebraicDataflowArchitectureModel/src/tests/NumberUtilTest.java +++ b/AlgebraicDataflowArchitectureModel/src/tests/NumberUtilTest.java @@ -16,33 +16,73 @@ assertTrue("Hex with long suffix (L)", NumberUtil.isHexNumber("0x123L")); assertTrue("Hex with long suffix (l)", NumberUtil.isHexNumber("0x123l")); assertTrue("Minimum valid hex", NumberUtil.isHexNumber("0x0")); - + // --- Invalid format numbers --- // Null and empty assertFalse("Null string", NumberUtil.isHexNumber(null)); assertFalse("Empty string", NumberUtil.isHexNumber("")); assertFalse("Prefix only", NumberUtil.isHexNumber("0x")); - + // Incorrect prefix assertFalse("Missing prefix", NumberUtil.isHexNumber("123")); assertFalse("Invalid prefix (0y)", NumberUtil.isHexNumber("0y123")); assertFalse("Leading space", NumberUtil.isHexNumber(" 0x123")); - + // Invalid characters assertFalse("Invalid character (g)", NumberUtil.isHexNumber("0x12g")); assertFalse("Decimal point", NumberUtil.isHexNumber("0x1.2")); - + // Underscore placement rules assertFalse("Underscore immediately after prefix", NumberUtil.isHexNumber("0x_123")); assertFalse("Trailing underscore", NumberUtil.isHexNumber("0x123_")); assertFalse("Trailing underscore with L", NumberUtil.isHexNumber("0x123_L")); - + // Long suffix rules assertFalse("L in middle", NumberUtil.isHexNumber("0x1L2")); assertFalse("Multiple L", NumberUtil.isHexNumber("0x1LL")); assertFalse("L with invalid char", NumberUtil.isHexNumber("0x1GL")); - + // No digits assertFalse("No hex digits (0xL)", NumberUtil.isHexNumber("0xL")); } + + @Test + public void testIsBinaryNumber() { + // --- Valid format numbers --- + assertTrue("Standard binary", NumberUtil.isBinaryNumber("0b10101010")); + assertTrue("Mixed case prefix", NumberUtil.isBinaryNumber("0B1100")); + assertTrue("Binary with underscores", NumberUtil.isBinaryNumber("0b1_0_1")); + assertTrue("Binary with long suffix (L)", NumberUtil.isBinaryNumber("0b101L")); + assertTrue("Binary with long suffix (l)", NumberUtil.isBinaryNumber("0b110l")); + assertTrue("Minimum valid binary", NumberUtil.isBinaryNumber("0b0")); + + // --- Invalid format numbers --- + // Null and empty + assertFalse("Null string", NumberUtil.isBinaryNumber(null)); + assertFalse("Empty string", NumberUtil.isBinaryNumber("")); + assertFalse("Prefix only", NumberUtil.isBinaryNumber("0b")); + + // Incorrect prefix + assertFalse("Missing prefix", NumberUtil.isBinaryNumber("101")); + assertFalse("Invalid prefix (0x)", NumberUtil.isBinaryNumber("0x101")); + assertFalse("Leading space", NumberUtil.isBinaryNumber(" 0b101")); + + // Invalid characters + assertFalse("Invalid character (2)", NumberUtil.isBinaryNumber("0b102")); + assertFalse("Invalid character (a)", NumberUtil.isBinaryNumber("0b10a")); + assertFalse("Decimal point", NumberUtil.isBinaryNumber("0b1.0")); + + // Underscore placement rules + assertFalse("Underscore immediately after prefix", NumberUtil.isBinaryNumber("0b_101")); + assertFalse("Trailing underscore", NumberUtil.isBinaryNumber("0b101_")); + assertFalse("Trailing underscore with L", NumberUtil.isBinaryNumber("0b101_L")); + + // Long suffix rules + assertFalse("L in middle", NumberUtil.isBinaryNumber("0b1L0")); + assertFalse("Multiple L", NumberUtil.isBinaryNumber("0b1LL")); + assertFalse("L with invalid char", NumberUtil.isBinaryNumber("0b12L")); + + // No digits + assertFalse("No binary digits (0bL)", NumberUtil.isBinaryNumber("0bL")); + } }