"The [[Float]] value of the given 
 [[string representation|string]] of a decimal floating 
 point number, or `null` if the string does not represent a 
 decimal floating point number.
 
 If the given string representation contains more digits
 than can be represented by a `Float`, then the least 
 significant digits are ignored.
 
 The syntax accepted by this method is the same as the 
 syntax for a `Float` literal in the Ceylon language 
 except that it may optionally begin with a sign 
 character (`+` or `-`) and may not contain grouping 
 underscore characters. That is, an optional sign character,
 followed by a string of decimal digits, followed by an
 optional decimal point and string of decimal digits, 
 followed by an optional decimal exponent, for example 
 `e+10` or `E-5`, or SI magnitude, `k`, `M`, `G`, `T`, `P`, 
 `m`, `u`, `n`, `p`, or `f`.
 
     Float: Sign? Digits ('.' Digits)? (Magnitude|Exponent)
     Sign: '+' | '-'
     Magnitude: 'k' | 'M' | 'G' | 'T' | 'P' | 'm' | 'u' | 'n' | 'p' | 'f'
     Exponent: ('e'|'E') Sign? Digits
     Digits: ('0'..'9')+"
see (`function Float.parse`)
tagged("Numbers", "Basic types")
deprecated("Use [[Float.parse]]")
shared Float? parseFloat(String string)
        => if (is Float result 
                = parseFloatInternal(string))
        then result
        else null;

class ParseFloatState 
        of start 
         | afterPlusMinus 
         | digitsBeforeDecimal 
         | afterJustDecimal 
         | afterDecimal 
         | digitsAfterDecimal 
         | afterE 
         | exponentDigits 
         | afterEPlusMinus 
         | afterSuffix 
         | invalid {
    
    shared new start {}
    shared new afterPlusMinus {}
    shared new digitsBeforeDecimal {}
    shared new afterJustDecimal {}
    shared new afterDecimal {}
    shared new digitsAfterDecimal {}
    shared new afterE {}
    shared new exponentDigits {}
    shared new afterEPlusMinus {}
    shared new afterSuffix {}
    shared new invalid {}
}

Float|ParseException parseFloatInternal(String string) {

    import ceylon.language {
        ParseFloatState {
            start, afterPlusMinus, digitsBeforeDecimal,
            afterJustDecimal, afterDecimal, digitsAfterDecimal,
            afterE, exponentDigits, afterEPlusMinus,
            afterSuffix, invalid
        }
    }
    
    // ("-"|"+")?
    // (Digit* "." Digit+) | (Digit+ "."?)
    // (("E"|"e") ("+"|"-")? Digit+) | suffix

    variable value state = start;
    variable value size = 0;
    variable Integer? suffixExponent = null;

    for (ch in string) {
        size++;
        state = switch (state)
        case (start)
            if (ch == '+' || ch == '-')
                then afterPlusMinus
            else if ('0' <= ch <= '9')
                then digitsBeforeDecimal
            else if (ch == '.')
                then afterJustDecimal
            else invalid
        case (afterPlusMinus)
            if ('0' <= ch <= '9')
                then digitsBeforeDecimal
            else if (ch == '.')
                then afterJustDecimal
            else invalid
        case (digitsBeforeDecimal)
            if ('0' <= ch <= '9')
                then digitsBeforeDecimal
            else if (ch == '.')
                then afterDecimal
            else if (ch == 'e' || ch == 'E')
                then afterE
            else if (ch in "PTGMkmunpf")
                then afterSuffix
            else invalid
        case (afterJustDecimal)
            if ('0' <= ch <= '9')
                then digitsAfterDecimal
            else invalid
        case (digitsAfterDecimal |
              afterDecimal)
            if ('0' <= ch <= '9')
                then digitsAfterDecimal
            else if (ch == 'e' || ch == 'E')
                then afterE
            else if (ch in "PTGMkmunpf")
                then afterSuffix
            else invalid
        case (afterE)
            if ('0' <= ch <= '9')
                then exponentDigits
            else if (ch == '+' || ch == '-')
                then afterEPlusMinus
            else invalid
        case (exponentDigits |
              afterEPlusMinus)
            if ('0' <= ch <= '9')
                then exponentDigits
            else invalid
        case (afterSuffix)
            invalid
        case (invalid)
            invalid;

        if (state == afterSuffix) {
            suffixExponent = parseSuffix(ch);
        }

        if (state == invalid) {
            return ParseException("illegal format for Float: unexpected character '``ch``'");
        }
    }

    if (!state in [digitsBeforeDecimal,
            afterDecimal, digitsAfterDecimal,
            exponentDigits, afterSuffix]) {
        return ParseException("illegal format for Float: unexpected end of string");
    }

    try {
        if (exists exponent = suffixExponent) {
            // Ceylon style magnitude suffix
            return nativeParseFloat(string[0:size-1] + "E" + exponent.string);
        }
        else {
            // may or may not have exponent
            return nativeParseFloat(string);
        }
    }
    catch (e) {
        return ParseException("illegal format for Float: " + e.message);
    }
}

Integer parseSuffix(Character suffix) {
    switch (suffix)
    case ('P') {
        return 15;
    }
    case ('T') {
        return 12;
    }
    case ('G') {
        return 9;
    }
    case ('M') {
        return 6;
    }
    case ('k') {
        return 3;
    }
    case ('m') {
        return -3;
    }
    case ('u') {
        return -6;
    }
    case ('n') {
        return -9;
    }
    case ('p') {
        return -12;
    }
    case ('f') {
        return -15;
    }
    else {
        "unrecognized SI magnitude"
        assert (false);
    }
}

native
Float nativeParseFloat(String string);

native("jvm")
Float nativeParseFloat(String string) {
    import java.lang {
        Double {
            parseDouble
        }
    }

    return parseDouble(string);
}

native("js")
Float nativeParseFloat(String string) {
    Float result;
    dynamic {
        result = nativeJSParseFloat(string);
    }
    if (result == 0.0 && string.occursAt(0, '-')) {
        return -0.0;
    }
    return result; 
}