diff --git a/parsley/shared/src/main/scala/parsley/token/Lexer.scala b/parsley/shared/src/main/scala/parsley/token/Lexer.scala index 9d29fefbb..b1412f655 100644 --- a/parsley/shared/src/main/scala/parsley/token/Lexer.scala +++ b/parsley/shared/src/main/scala/parsley/token/Lexer.scala @@ -664,7 +664,7 @@ final class Lexer(desc: descriptions.LexicalDesc, errConfig: errors.ErrorConfig) private [Lexer] val _natural = new UnsignedInteger(desc.numericDesc, errConfig, generic) private [Lexer] val _integer = new SignedInteger(desc.numericDesc, _natural, errConfig) - private [Lexer] val _positiveReal = new UnsignedReal(desc.numericDesc, _natural, errConfig, generic) + private [Lexer] val _positiveReal = new UnsignedReal(desc.numericDesc, errConfig, generic) private [Lexer] val _real = new SignedReal(desc.numericDesc, _positiveReal, errConfig) private [Lexer] val _unsignedCombined = new UnsignedCombined(desc.numericDesc, _natural, _positiveReal, errConfig) private [Lexer] val _signedCombined = new SignedCombined(desc.numericDesc, _unsignedCombined, errConfig) diff --git a/parsley/shared/src/main/scala/parsley/token/descriptions/NumericDesc.scala b/parsley/shared/src/main/scala/parsley/token/descriptions/NumericDesc.scala index b7e63812b..540842868 100644 --- a/parsley/shared/src/main/scala/parsley/token/descriptions/NumericDesc.scala +++ b/parsley/shared/src/main/scala/parsley/token/descriptions/NumericDesc.scala @@ -55,12 +55,14 @@ object ExponentDesc { * @param chars the set of possible characters that can start an exponent part of a literal. * @param base the base of the exponent: for instance `e3` with `base = 10` would represent multiplication by 1000. * @param positiveSign are positive (`+`) signs allowed, required, or illegal in front of the exponent? + * @param leadingZerosAllowed are extraneous zeros allowed at the start of the exponent? * @since 4.0.0 */ final case class Supported(compulsory: Boolean, chars: Set[Char], base: Int, - positiveSign: PlusSignPresence + positiveSign: PlusSignPresence, + leadingZerosAllowed: Boolean, ) extends ExponentDesc { require(chars.nonEmpty, "The characters used for floating point exponents must not be empty") } @@ -137,7 +139,7 @@ final case class NumericDesc (literalBreakChar: BreakCharDesc, decimalExponentDesc: ExponentDesc, hexadecimalExponentDesc: ExponentDesc, octalExponentDesc: ExponentDesc, - binaryExponentDesc: ExponentDesc + binaryExponentDesc: ExponentDesc, ) { private def boolToInt(x: Boolean): Int = if (x) 1 else 0 @@ -199,10 +201,10 @@ object NumericDesc { * hexadecimalLeads = Set('x', 'X') * octalLeads = Set('o', 'O') * binaryLeads = Set('b', 'B') - * decimalExponentDesc = ExponentDesc.Supported(compulsory = false, chars = Set('e', 'E'), base = 10, positiveSign = PlusSignPresence.Optional) - * hexadecimalExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional) - * octalExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('e', 'E', 'p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional) - * binaryExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('e', 'E', 'p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional) + * decimalExponentDesc = ExponentDesc.Supported(compulsory = false, chars = Set('e', 'E'), base = 10, positiveSign = PlusSignPresence.Optional, leadingZerosAllowed = true) + * hexadecimalExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional, leadingZerosAllowed = true) + * octalExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('e', 'E', 'p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional, leadingZerosAllowed = true) + * binaryExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('e', 'E', 'p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional, leadingZerosAllowed = true) * }}} * * @since 4.0.0 @@ -225,9 +227,13 @@ object NumericDesc { octalLeads = Set('o', 'O'), binaryLeads = Set('b', 'B'), // exponents - decimalExponentDesc = ExponentDesc.Supported(compulsory = false, chars = Set('e', 'E'), base = 10, positiveSign = PlusSignPresence.Optional), - hexadecimalExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional), - octalExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('e', 'E', 'p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional), - binaryExponentDesc = ExponentDesc.Supported(compulsory = true, chars = Set('e', 'E', 'p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional) + decimalExponentDesc = + ExponentDesc.Supported(compulsory = false, chars = Set('e', 'E'), base = 10, positiveSign = PlusSignPresence.Optional, leadingZerosAllowed = true), + hexadecimalExponentDesc = + ExponentDesc.Supported(compulsory = true, chars = Set('p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional, leadingZerosAllowed = true), + octalExponentDesc = + ExponentDesc.Supported(compulsory = true, chars = Set('e', 'E', 'p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional, leadingZerosAllowed = true), + binaryExponentDesc = + ExponentDesc.Supported(compulsory = true, chars = Set('e', 'E', 'p', 'P'), base = 2, positiveSign = PlusSignPresence.Optional, leadingZerosAllowed = true), ) } diff --git a/parsley/shared/src/main/scala/parsley/token/numeric/UnsignedReal.scala b/parsley/shared/src/main/scala/parsley/token/numeric/UnsignedReal.scala index f13403289..85fe2a261 100644 --- a/parsley/shared/src/main/scala/parsley/token/numeric/UnsignedReal.scala +++ b/parsley/shared/src/main/scala/parsley/token/numeric/UnsignedReal.scala @@ -15,7 +15,7 @@ import parsley.syntax.character.charLift import parsley.token.descriptions.numeric.{BreakCharDesc, ExponentDesc, NumericDesc} import parsley.token.errors.{ErrorConfig, LabelConfig} -private [token] final class UnsignedReal(desc: NumericDesc, natural: UnsignedInteger, err: ErrorConfig, generic: Generic) extends RealParsers(err) { +private [token] final class UnsignedReal(desc: NumericDesc, err: ErrorConfig, generic: Generic) extends RealParsers(err) { override lazy val _decimal: Parsley[BigDecimal] = atomic(ofRadix(10, digit, err.labelRealDecimalEnd)) override lazy val _hexadecimal: Parsley[BigDecimal] = atomic('0' *> noZeroHexadecimal) override lazy val _octal: Parsley[BigDecimal] = atomic('0' *> noZeroOctal) @@ -104,12 +104,14 @@ private [token] final class UnsignedReal(desc: NumericDesc, natural: UnsignedInt } } val (requiredExponent, exponent, base) = expDesc match { - case ExponentDesc.Supported(compulsory, exp, base, sign) => + case ExponentDesc.Supported(compulsory, exp, base, sign, leadingZeros) => val expErr = new ErrorConfig { override def labelIntegerSignedDecimal(bits: Int) = err.labelRealExponentEnd.orElse(endLabel) override def labelIntegerDecimalEnd = err.labelRealExponentEnd.orElse(endLabel) } - val integer = new SignedInteger(desc.copy(positiveSign = sign), natural, expErr) + val expIntDesc = desc.copy(positiveSign = sign, leadingZerosAllowed = leadingZeros) + val natural = new UnsignedInteger(expIntDesc, expErr, generic) + val integer = new SignedInteger(expIntDesc, natural, expErr) val exponent = err.labelRealExponent.orElse(endLabel)(oneOf(exp)) *> integer.decimal32 if (compulsory) (exponent, exponent, base) else (exponent, exponent <|> pure(0), base) diff --git a/parsley/shared/src/test/scala/parsley/token/DescriptionRequireTests.scala b/parsley/shared/src/test/scala/parsley/token/DescriptionRequireTests.scala index 94e1069fe..4b749ab4e 100644 --- a/parsley/shared/src/test/scala/parsley/token/DescriptionRequireTests.scala +++ b/parsley/shared/src/test/scala/parsley/token/DescriptionRequireTests.scala @@ -17,8 +17,8 @@ class DescriptionRequireTests extends ParsleyTest { } "ExponentDesc.Supported" should "not allow for empty exponents" in { - an [IllegalArgumentException] should be thrownBy ExponentDesc.Supported(false, Set.empty, 10, PlusSignPresence.Illegal) - an [IllegalArgumentException] should be thrownBy ExponentDesc.Supported(false, Set.empty, 10, PlusSignPresence.Optional) + an [IllegalArgumentException] should be thrownBy ExponentDesc.Supported(false, Set.empty, 10, PlusSignPresence.Illegal, true) + an [IllegalArgumentException] should be thrownBy ExponentDesc.Supported(false, Set.empty, 10, PlusSignPresence.Optional, true) } "NumericDesc" should "not allow for multiple prefixless descriptions" in { diff --git a/parsley/shared/src/test/scala/parsley/token/numeric/RealTests.scala b/parsley/shared/src/test/scala/parsley/token/numeric/RealTests.scala index e77259546..5deb81ba4 100644 --- a/parsley/shared/src/test/scala/parsley/token/numeric/RealTests.scala +++ b/parsley/shared/src/test/scala/parsley/token/numeric/RealTests.scala @@ -16,7 +16,7 @@ import org.scalactic.source.Position class RealTests extends ParsleyTest { val errConfig = new ErrorConfig val generic = new Generic(errConfig) - private def makeReal(desc: NumericDesc) = new LexemeReal(new SignedReal(desc, new UnsignedReal(desc, new UnsignedInteger(desc, errConfig, generic), errConfig, generic), errConfig), LexemeImpl.empty, errConfig) + private def makeReal(desc: NumericDesc) = new LexemeReal(new SignedReal(desc, new UnsignedReal(desc, errConfig, generic), errConfig), LexemeImpl.empty, errConfig) val plain = NumericDesc.plain.copy(decimalExponentDesc = NoExponents, hexadecimalExponentDesc = NoExponents, octalExponentDesc = NoExponents, binaryExponentDesc = NoExponents) @@ -77,7 +77,7 @@ class RealTests extends ParsleyTest { decimalCases(withTrailingDotBreak)("0._1" -> None) } it should "allow for scientific notation when configured" in { - val plainExp = plain.copy(decimalExponentDesc = ExponentDesc.Supported(false, Set('e', 'E'), 10, PlusSignPresence.Optional)) + val plainExp = plain.copy(decimalExponentDesc = ExponentDesc.Supported(false, Set('e', 'E'), 10, PlusSignPresence.Optional, true)) val withExtremeDotExpDesc = plainExp.copy(leadingDotAllowed = true, trailingDotAllowed = true) decimalCases(plainExp)( "3.0e3" -> Some(BigDecimal("3000.0")), @@ -111,10 +111,11 @@ class RealTests extends ParsleyTest { ) } it should "allow for scientific notation when configured" in { - val plainExp = plain.copy(hexadecimalExponentDesc = ExponentDesc.Supported(true, Set('p', 'P'), 2, PlusSignPresence.Optional)) + val plainExp = plain.copy(hexadecimalExponentDesc = ExponentDesc.Supported(true, Set('p', 'P'), 2, PlusSignPresence.Optional, false)) val withExtremeDotExpDesc = plainExp.copy(leadingDotAllowed = true, trailingDotAllowed = true) hexadecimalCases(plainExp)( "0x0.f" -> None, + "0x0.fp0002" -> None, "0x0.fp0" -> Some(BigDecimal("0.9375")), "0x0.fP1" -> Some(BigDecimal("1.875")), "0xa.c7p-2" -> Some(BigDecimal("10.77734375")/4), @@ -147,11 +148,11 @@ class RealTests extends ParsleyTest { ) } it should "allow for scientific notation when configured" in { - val plainExp = plain.copy(octalExponentDesc = ExponentDesc.Supported(true, Set('e', 'E'), 10, PlusSignPresence.Required)) + val plainExp = plain.copy(octalExponentDesc = ExponentDesc.Supported(true, Set('e', 'E'), 10, PlusSignPresence.Required, true)) val withExtremeDotExpDesc = plainExp.copy(leadingDotAllowed = true, trailingDotAllowed = true) octalCases(plainExp)( "0o0.6" -> None, - "0o0.4e+0" -> Some(BigDecimal("0.5")), + "0o0.4e+000" -> Some(BigDecimal("0.5")), "0o0.4E+1" -> Some(BigDecimal("5.0")), "0o5.42e-2" -> Some(BigDecimal("5.53125")/100), ) @@ -183,7 +184,7 @@ class RealTests extends ParsleyTest { ) } it should "allow for scientific notation when configured" in { - val plainExp = plain.copy(binaryExponentDesc = ExponentDesc.Supported(true, Set('p', 'P'), 2, PlusSignPresence.Illegal), + val plainExp = plain.copy(binaryExponentDesc = ExponentDesc.Supported(true, Set('p', 'P'), 2, PlusSignPresence.Illegal, true), literalBreakChar = BreakCharDesc.Supported('_', false)) val withExtremeDotExpDesc = plainExp.copy(leadingDotAllowed = true, trailingDotAllowed = true) binaryCases(plainExp)( @@ -221,9 +222,9 @@ class RealTests extends ParsleyTest { // bounded things (only decimal) "bounded reals" should "not permit illegal numbers" in { - val represent = makeReal(plain.copy(decimalExponentDesc = ExponentDesc.Supported(false, Set('e', 'E'), 10, PlusSignPresence.Optional), - hexadecimalExponentDesc = ExponentDesc.Supported(true, Set('p'), 2, PlusSignPresence.Required), - binaryExponentDesc = ExponentDesc.Supported(true, Set('p'), 2, PlusSignPresence.Required))) + val represent = makeReal(plain.copy(decimalExponentDesc = ExponentDesc.Supported(false, Set('e', 'E'), 10, PlusSignPresence.Optional, true), + hexadecimalExponentDesc = ExponentDesc.Supported(true, Set('p'), 2, PlusSignPresence.Required, true), + binaryExponentDesc = ExponentDesc.Supported(true, Set('p'), 2, PlusSignPresence.Required, true))) cases(represent.decimalDouble)( "0.4" -> Some(0.4), "0.33333333333333333333333" -> Some(0.33333333333333333333333),