Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions Src/IronPython/Runtime/FormattingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

#nullable enable

using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;

namespace IronPython.Runtime {
internal static class FormattingHelper {
private static NumberFormatInfo _invariantUnderscoreSeperatorInfo;
private static NumberFormatInfo? _invariantUnderscoreSeperatorInfo;

/// <summary>
/// Helper NumberFormatInfo for use by int/BigInteger __format__ routines
Expand All @@ -33,10 +36,10 @@ public static NumberFormatInfo InvariantUnderscoreNumberInfo {
}
}

public static string/*!*/ ToCultureString<T>(T/*!*/ val, NumberFormatInfo/*!*/ nfi, StringFormatSpec spec, int? overrideWidth = null) {
public static string/*!*/ ToCultureString<T>(T/*!*/ val, NumberFormatInfo/*!*/ nfi, StringFormatSpec spec, int? overrideWidth = null) where T : notnull {
string separator = nfi.NumberGroupSeparator;
int[] separatorLocations = nfi.NumberGroupSizes;
string digits = val.ToString();
string digits = val.ToString()!;

// If we're adding leading zeros, we need to know how
// many we need.
Expand Down Expand Up @@ -126,5 +129,58 @@ public static NumberFormatInfo InvariantUnderscoreNumberInfo {

return digits;
}

public static string AddUnderscores(string digits, StringFormatSpec spec, bool isNegative) {
var length = digits.Length + (digits.Length - 1) / 4; // length including minimum number of underscores

int idx;
var fillLength = 0;
if (spec.Fill == '0') {
if (spec.Width > length) {
var width = spec.Width.Value;
if (isNegative || spec.Sign != null && spec.Sign != '-') width--;
fillLength = width - length;
length = width;
}

// index of first underscore
idx = length % 5;
if (idx == 0) {
idx = 1;
fillLength++;
length++;
}
} else {
// index of first underscore
idx = length % 5;
if (idx == 0) {
idx = 1;
length++;
}
}

var sb = new StringBuilder(length);

for (int i = 0; i < fillLength; i++, idx--) {
if (idx == 0) {
sb.Append('_');
idx = 5;
} else {
sb.Append('0');
}
}
int j = 0;
for (int i = fillLength; i < length; i++, idx--) {
if (idx == 0) {
sb.Append('_');
idx = 5;
} else {
sb.Append(digits[j++]);
}
}
Debug.Assert(j == digits.Length);

return sb.ToString();
}
}
}
62 changes: 36 additions & 26 deletions Src/IronPython/Runtime/Operations/BigIntegerOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,7 @@ public static BigInteger ToBigInteger(BigInteger self) {

if (spec.Fill == '0' && spec.Width > 1) {
digits = FormattingHelper.ToCultureString(val, culture.NumberFormat, spec, (spec.Sign != null && spec.Sign != '-' || self < 0) ? spec.Width - 1 : null);
}
else {
} else {
digits = FormattingHelper.ToCultureString(val, culture.NumberFormat, spec);
}
break;
Expand Down Expand Up @@ -790,24 +789,36 @@ public static BigInteger ToBigInteger(BigInteger self) {
digits = DoubleOps.DoubleToFormatString(context, ToDouble(val), spec);
break;
case 'X':
digits = AbsToHex(val, lowercase: false);
digits = ToHexDigits(val, lowercase: false);
if (spec.ThousandsUnderscore) {
digits = FormattingHelper.AddUnderscores(digits, spec, self.IsNegative());
}
break;
case 'x':
digits = AbsToHex(val, lowercase: true);
digits = ToHexDigits(val, lowercase: true);
if (spec.ThousandsUnderscore) {
digits = FormattingHelper.AddUnderscores(digits, spec, self.IsNegative());
}
break;
case 'o': // octal
digits = ToOctal(val, lowercase: true);
digits = ToOctalDigits(val);
if (spec.ThousandsUnderscore) {
digits = FormattingHelper.AddUnderscores(digits, spec, self.IsNegative());
}
break;
case 'b': // binary
digits = ToBinary(val, includeType: false, lowercase: true);
digits = ToBinaryDigits(val);
if (spec.ThousandsUnderscore) {
digits = FormattingHelper.AddUnderscores(digits, spec, self.IsNegative());
}
break;
case 'c': // single char
int iVal;
if (spec.Sign != null) {
throw PythonOps.ValueError("Sign not allowed with integer format specifier 'c'");
} else if (!self.AsInt32(out iVal)) {
throw PythonOps.OverflowError("Python int too large to convert to System.Int32");
} else if(iVal < 0 || iVal > 0x10ffff) {
} else if (iVal < 0 || iVal > 0x10ffff) {
throw PythonOps.OverflowError("%c arg not in range(0x110000)");
}

Expand Down Expand Up @@ -1025,8 +1036,10 @@ public static TypeCode GetTypeCode(BigInteger self) {

#region Helpers

/// <summary>
/// Unlike ConvertToDouble, this method produces a Python-specific overflow error messge.
/// </summary>
internal static double ToDouble(BigInteger self) {
// Unlike ConvertToDouble, this method produces a Python-specific overflow error messge.
if (MathUtils.TryToFloat64(self, out double res)) {
return res;
}
Expand All @@ -1037,27 +1050,24 @@ internal static string AbsToHex(BigInteger val, bool lowercase) {
return ToDigits(val, 16, lowercase);
}

private static string ToOctal(BigInteger val, bool lowercase) {
return ToDigits(val, 8, lowercase);
private static string ToHexDigits(BigInteger val, bool lowercase) {
Debug.Assert(val >= 0);
return ToDigits(val, 16, lower: lowercase);
}

internal static string ToBinary(BigInteger val) {
string res = ToBinary(BigInteger.Abs(val), true, true);
if (val.IsNegative()) {
res = "-" + res;
}
return res;
private static string ToOctalDigits(BigInteger val) {
Debug.Assert(val >= 0);
return ToDigits(val, 8, lower: false);
}

private static string ToBinary(BigInteger val, bool includeType, bool lowercase) {
Debug.Assert(!val.IsNegative());

string digits = ToDigits(val, 2, lowercase);
private static string ToBinaryDigits(BigInteger val) {
Debug.Assert(val >= 0);
return ToDigits(val, 2, lower: false);
}

if (includeType) {
digits = (lowercase ? "0b" : "0B") + digits;
}
return digits;
internal static string ToBinary(BigInteger val) {
var digits = ToBinaryDigits(BigInteger.Abs(val));
return ((val < 0) ? "-0b" : "0b") + digits;
}

private static string/*!*/ ToDigits(BigInteger/*!*/ val, int radix, bool lower) {
Expand All @@ -1066,12 +1076,12 @@ private static string ToBinary(BigInteger val, bool includeType, bool lowercase)
}

StringBuilder str = new StringBuilder();
char a = lower ? 'a' : 'A';

while (val != 0) {
int digit = (int)(val % radix);
if (digit < 10) str.Append((char)((digit) + '0'));
else if (lower) str.Append((char)((digit - 10) + 'a'));
else str.Append((char)((digit - 10) + 'A'));
else str.Append((char)((digit - 10) + a));

val /= radix;
}
Expand Down
115 changes: 44 additions & 71 deletions Src/IronPython/Runtime/Operations/IntOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,16 +262,28 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotNone] st
digits = DoubleOps.DoubleToFormatString(context, val, spec);
break;
case 'X':
digits = ToHex(val, lowercase: false);
digits = ToHexDigits(val, lowercase: false);
if (spec.ThousandsUnderscore) {
digits = FormattingHelper.AddUnderscores(digits, spec, self < 0);
}
break;
case 'x':
digits = ToHex(val, lowercase: true);
digits = ToHexDigits(val, lowercase: true);
if (spec.ThousandsUnderscore) {
digits = FormattingHelper.AddUnderscores(digits, spec, self < 0);
}
break;
case 'o': // octal
digits = ToOctal(val, lowercase: true);
digits = ToOctalDigits(val);
if (spec.ThousandsUnderscore) {
digits = FormattingHelper.AddUnderscores(digits, spec, self < 0);
}
break;
case 'b': // binary
digits = ToBinary(val, includeType: false);
digits = ToBinaryDigits(val) ;
if (spec.ThousandsUnderscore) {
digits = FormattingHelper.AddUnderscores(digits, spec, self < 0);
}
break;
case 'c': // single char
if (spec.Sign != null) {
Expand Down Expand Up @@ -306,86 +318,47 @@ public static object from_bytes(CodeContext context, PythonType type, object byt

#region Helpers

private static string ToHex(int self, bool lowercase) {
string digits;
if (self != Int32.MinValue) {
int val = self;
if (self < 0) {
val = -self;
}
digits = val.ToString(lowercase ? "x" : "X", CultureInfo.InvariantCulture);
} else {
digits = "80000000";
}

return digits;
private static string ToHexDigits(int val, bool lowercase) {
Debug.Assert(val >= 0);
return val.ToString(lowercase ? "x" : "X", CultureInfo.InvariantCulture);
}

private static string ToOctal(int self, bool lowercase) {
string digits;
if (self == 0) {
digits = "0";
} else if (self != Int32.MinValue) {
int val = self;
if (self < 0) {
val = -self;
}
private static string ToOctalDigits(int val) {
Debug.Assert(val >= 0);
if (val == 0) return "0";

StringBuilder sbo = new StringBuilder();
for (int i = 30; i >= 0; i -= 3) {
char value = (char)('0' + (val >> i & 0x07));
if (value != '0' || sbo.Length > 0) {
sbo.Append(value);
}
StringBuilder sb = new StringBuilder();
for (int i = 30; i >= 0; i -= 3) {
char value = (char)('0' + (val >> i & 0x07));
if (value != '0' || sb.Length > 0) {
sb.Append(value);
}
digits = sbo.ToString();
} else {
digits = "20000000000";
}

return digits;
return sb.ToString();
}

internal static string ToBinary(int self) {
if (self == Int32.MinValue) {
return "-0b10000000000000000000000000000000";
}
private static string ToBinaryDigits(int val) {
Debug.Assert(val >= 0);
if (val == 0) return "0";

string res = ToBinary(self, true);
if (self < 0) {
res = "-" + res;
StringBuilder sb = new StringBuilder();
for (int i = 31; i >= 0; i--) {
if ((val & (1 << i)) != 0) {
sb.Append('1');
} else if (sb.Length != 0) {
sb.Append('0');
}
}
return res;
return sb.ToString();
}

private static string ToBinary(int self, bool includeType) {
string digits;
if (self == 0) {
digits = "0";
} else if (self != Int32.MinValue) {
StringBuilder sbb = new StringBuilder();

int val = self;
if (self < 0) {
val = -self;
}

for (int i = 31; i >= 0; i--) {
if ((val & (1 << i)) != 0) {
sbb.Append('1');
} else if (sbb.Length != 0) {
sbb.Append('0');
}
}
digits = sbb.ToString();
} else {
digits = "10000000000000000000000000000000";
internal static string ToBinary(int val) {
if (val == int.MinValue) {
return "-0b10000000000000000000000000000000";
}

if (includeType) {
digits = "0b" + digits;
}
return digits;
var digits = ToBinaryDigits(Math.Abs(val));
return ((val < 0) ? "-0b" : "0b") + digits;
}

#endregion
Expand Down
15 changes: 13 additions & 2 deletions Src/IronPython/Runtime/StringFormatSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,15 @@ private StringFormatSpec(char? fill, char? alignment, char? sign, int? width, bo
curOffset++;
}

// TODO: read optional underscore (new in 3.6)
// read optional underscore
if (curOffset != formatSpec.Length &&
formatSpec[curOffset] == '_') {
thousandsUnderscore = true;
curOffset++;
if (thousandsComma || curOffset != formatSpec.Length && formatSpec[curOffset] == ',') {
throw PythonOps.ValueError("Cannot specify both ',' and '_'");
}
}

// read precision
if (curOffset != formatSpec.Length &&
Expand Down Expand Up @@ -191,11 +199,14 @@ private StringFormatSpec(char? fill, char? alignment, char? sign, int? width, bo
break;
default:
throw PythonOps.ValueError("Cannot specify '_' with '{0}'", type);

}
}
}

if (curOffset != formatSpec.Length) {
throw PythonOps.ValueError("Invalid format specifier '{0}'", formatSpec);
}

return new StringFormatSpec(
fill,
align,
Expand Down
Loading