All Downloads are FREE. Search and download functionalities are using the official Maven repository.

parsley.token.descriptions.NumericDesc.scala Maven / Gradle / Ivy

There is a newer version: 5.0.0-M6
Show newest version
/*
 * Copyright 2020 Parsley Contributors 
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
package parsley.token.descriptions

/** This class, and its subtypes, describe whether or not the plus sign (`+`) is allowed
  * in a specific position.
  *
  * @since 4.0.0
  */
sealed abstract class PlusSignPresence
/** This object contains the concrete subtypes for `PlusSignPresence`.
  *
  * @since 4.0.0
  */
object PlusSignPresence {
    /** When writing a non-negative literal, a `+` is mandatory before the literal.
      *
      * @since 4.0.0
      */
    case object Required extends PlusSignPresence
    /** When writing a non-negative literal, a `+` can be added, but is not required.
      *
      * @since 4.0.0
      */
    case object Optional extends PlusSignPresence
    /** Positive literals must not be prefixed by a `+`.
      *
      * @since 4.0.0
      */
    case object Illegal extends PlusSignPresence
}

/** This class, and its subtypes, describe how scientific exponent notation can be used within real literals.
  *
  * @since 4.0.0
  */
sealed abstract class ExponentDesc
/** This object contains the concrete subtypes of `ExponentDesc`.
  *
  * @since 4.0.0
  */
object ExponentDesc {
    /** Exponents are not supported.
      *
      * @since 4.0.0
      */
    case object NoExponents extends ExponentDesc
    /** Exponents are supported, which may be compulsory. The base of the exponent can vary, as can whether a positive (`+`) sign
      * is allowed before the exponent.
      *
      * @param compulsory is an exponent ''required'' for the literal (at a specific base) to be valid?
      * @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,
                               leadingZerosAllowed: Boolean,
                              ) extends ExponentDesc {
        require(chars.nonEmpty, "The characters used for floating point exponents must not be empty")
    }
}

/** This class, and its subtypes, describe how break characters are supported within literals.
  *
  * @since 4.0.0
  */
sealed abstract class BreakCharDesc
/** This object contains the concrete subtypes of `BreakCharDesc`.
  *
  * @since 4.0.0
  */
object BreakCharDesc {
    /** Literals cannot be broken.
      *
      * @since 4.0.0
      */
    case object NoBreakChar extends BreakCharDesc
    /** Literals may be broken, and this break may be legal after a non-decimal literal prefix
      *
      * @param breakChar the character allowed to break a literal (often `_`).
      * @param allowedAfterNonDecimalPrefix is it possible to write, say, `0x_300`?
      * @since 4.0.0
      */
    final case class Supported(breakChar: Char, allowedAfterNonDecimalPrefix: Boolean) extends BreakCharDesc
}

// TODO: configurable dot?
/** This class describes how numeric literals, in different bases, should be processed lexically.
  *
  * @param literalBreakChar describes if breaks can be found within numeric literals.
  * @param leadingDotAllowed can a real number omit a leading 0 before the point?
  * @param trailingDotAllowed can a real number omit a trailing 0 after the point?
  * @param leadingZerosAllowed are extraneous zeros allowed at the start of decimal numbers?
  * @param positiveSign describes if positive (`+`) signs are allowed, compulsory, or illegal.
  * @param integerNumbersCanBeHexadecimal $genericInt hexadecimal?
  * @param integerNumbersCanBeOctal $genericInt octal?
  * @param integerNumbersCanBeBinary $genericInt binary?
  * @param realNumbersCanBeHexadecimal $genericReal hexadecimal?
  * @param realNumbersCanBeOctal $genericReal octal?
  * @param realNumbersCanBeBinary $genericReal binary?
  * @param hexadecimalLeads what characters begin a hexadecimal literal following a `0` (may be empty).
  * @param octalLeads what characters begin an octal literal following a `0` (may be empty).
  * @param binaryLeads what characters begin a binary literal following a `0` (may be empty).
  * @param decimalExponentDesc $genericExp decimal literals.
  * @param hexadecimalExponentDesc $genericExp hexadecimal literals.
  * @param octalExponentDesc $genericExp octal literals.
  * @param binaryExponentDesc $genericExp binary literals.
  * @since 4.0.0
  *
  * @define genericInt is it possible for generic "integer numbers" to be
  * @define genericReal is it possible for generic "real numbers" to be
  * @define genericExp describes how scientific exponent notation should work for
  */
final case class NumericDesc (literalBreakChar: BreakCharDesc,
                              leadingDotAllowed: Boolean,
                              trailingDotAllowed: Boolean,
                              leadingZerosAllowed: Boolean,
                              positiveSign: PlusSignPresence,
                              // generic number
                              integerNumbersCanBeHexadecimal: Boolean,
                              integerNumbersCanBeOctal: Boolean,
                              integerNumbersCanBeBinary: Boolean,
                              realNumbersCanBeHexadecimal: Boolean,
                              realNumbersCanBeOctal: Boolean,
                              realNumbersCanBeBinary: Boolean,
                              // special literals
                              hexadecimalLeads: Set[Char],
                              octalLeads: Set[Char],
                              binaryLeads: Set[Char],
                              // exponents
                              decimalExponentDesc: ExponentDesc,
                              hexadecimalExponentDesc: ExponentDesc,
                              octalExponentDesc: ExponentDesc,
                              binaryExponentDesc: ExponentDesc,
                             ) {
    private def boolToInt(x: Boolean): Int = if (x) 1 else 0

    locally {
        val intHex = boolToInt(integerNumbersCanBeHexadecimal)
        val intOct = boolToInt(integerNumbersCanBeOctal)
        val intBin = boolToInt(integerNumbersCanBeBinary)
        val realHex = boolToInt(realNumbersCanBeHexadecimal)
        val realOct = boolToInt(realNumbersCanBeOctal)
        val realBin = boolToInt(realNumbersCanBeBinary)
        // There can, for either ints or real /number/s be at most 1 empty prefix special
        // they can all not require prefixes when they are used explicitly
        val emptyHex = boolToInt(hexadecimalLeads.isEmpty)
        val emptyOct = boolToInt(octalLeads.isEmpty)
        val emptyBin = boolToInt(binaryLeads.isEmpty)
        val numEmptyInt = intHex * emptyHex + intOct * emptyOct + intBin * emptyBin
        val numEmptyReal =  realHex * emptyHex + realOct * emptyOct + realBin * emptyBin

        require(numEmptyInt <= 1 && numEmptyReal <= 1,
                "More than one of hexadecimal, octal, or binary do not use a prefix in integer or real numbers, this is not allowed as it is ambiguous")

        require(numEmptyInt + numEmptyReal == 0 || !leadingZerosAllowed,
                "One of hexadecimal, octal, or binary do not use a prefix, so decimal numbers must not allow for leading zeros as it is ambiguous")
    }


    private [token] def exponentDescForRadix(x: Int): ExponentDesc = (x: @unchecked) match {
        case 10 => decimalExponentDesc
        case 16 => hexadecimalExponentDesc
        case 8 => octalExponentDesc
        case 2 => binaryExponentDesc
    }

    private [token] def decimalIntegersOnly: Boolean = !(integerNumbersCanBeBinary || integerNumbersCanBeHexadecimal || integerNumbersCanBeOctal)
    private [token] def decimalRealsOnly: Boolean = !(realNumbersCanBeBinary || realNumbersCanBeHexadecimal || realNumbersCanBeOctal)
}

/** This object contains any preconfigured text definitions.
  *
  * @since 4.0.0
  */
object NumericDesc {
    /** Plain definition of numeric literals. Supports leading zeros; hexadecimal, octal, and binary notation;
      * and exponent notation for all four bases too. Only hexadecimal and octal are enabled for integer numbers,
      * and only decimal for real numbers.
      *
      * {{{
      * literalBreakChar = BreakCharDesc.NoBreakChar
      * leadingDotAllowed = false
      * trailingDotAllowed = false
      * leadingZerosAllowed = true
      * positiveSign = PlusSignPresence.Optional
      * integerNumbersCanBeHexadecimal = true
      * integerNumbersCanBeOctal = true
      * integerNumbersCanBeBinary = false
      * realNumbersCanBeHexadecimal = false
      * realNumbersCanBeOctal = false
      * realNumbersCanBeBinary = false
      * 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, 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
      */
    val plain: NumericDesc = NumericDesc(
        literalBreakChar = BreakCharDesc.NoBreakChar,
        leadingDotAllowed = false,
        trailingDotAllowed = false,
        leadingZerosAllowed = true,
        positiveSign = PlusSignPresence.Optional,
        // generic number
        integerNumbersCanBeHexadecimal = true,
        integerNumbersCanBeOctal = true,
        integerNumbersCanBeBinary = false,
        realNumbersCanBeHexadecimal = false,
        realNumbersCanBeOctal = false,
        realNumbersCanBeBinary = false,
        // special literals
        hexadecimalLeads = Set('x', 'X'),
        octalLeads = Set('o', 'O'),
        binaryLeads = Set('b', 'B'),
        // exponents
        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),
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy