Skip to content

Commit

Permalink
Skylark: int() can now declare a base parameter.
Browse files Browse the repository at this point in the history
--
MOS_MIGRATED_REVID=126434299
  • Loading branch information
fweikert authored and lberki committed Jul 4, 2016
1 parent 99a2b2e commit bc1ff69
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 28 deletions.
112 changes: 86 additions & 26 deletions src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
Expand Down Expand Up @@ -1762,33 +1763,92 @@ public Boolean invoke(Object x) throws EvalException {
}
};

@SkylarkSignature(name = "int", returnType = Integer.class, doc = "Converts a value to int. "
+ "If the argument is a string, it is converted using base 10 and raises an error if the "
+ "conversion fails. If the argument is a bool, it returns 0 (False) or 1 (True). "
+ "If the argument is an int, it is simply returned."
+ "<pre class=\"language-python\">int(\"123\") == 123</pre>",
parameters = {
@Param(name = "x", type = Object.class, doc = "The string to convert.")},
useLocation = true)
private static final BuiltinFunction int_ = new BuiltinFunction("int") {
public Integer invoke(Object x, Location loc) throws EvalException {
if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? 1 : 0;
} else if (x instanceof Integer) {
return (Integer) x;
} else if (x instanceof String) {
try {
return Integer.parseInt((String) x);
} catch (NumberFormatException e) {
throw new EvalException(loc,
"invalid literal for int(): " + Printer.repr(x));
@SkylarkSignature(
name = "int",
returnType = Integer.class,
doc =
"Converts a value to int. "
+ "If the argument is a string, it is converted using the given base and raises an "
+ "error if the conversion fails. "
+ "The base can be between 2 and 36 (inclusive) and defaults to 10. "
+ "The value can be prefixed with 0b/0o/ox to represent values in base 2/8/16. "
+ "If such a prefix is present, a base of 0 can be used to automatically determine the "
+ "correct base: "
+ "<pre class=\"language-python\">int(\"0xFF\", 0) == int(\"0xFF\", 16) == 255</pre>"
+ "If the argument is a bool, it returns 0 (False) or 1 (True). "
+ "If the argument is an int, it is simply returned."
+ "<pre class=\"language-python\">int(\"123\") == 123</pre>",
parameters = {
@Param(name = "x", type = Object.class, doc = "The string to convert."),
@Param(
name = "base",
type = Integer.class,
defaultValue = "10",
doc = "The base of the string."
)
},
useLocation = true
)
private static final BuiltinFunction int_ =
new BuiltinFunction("int") {
private final ImmutableMap<String, Integer> intPrefixes =
ImmutableMap.of("0b", 2, "0o", 8, "0x", 16);

@SuppressWarnings("unused")
public Integer invoke(Object x, Integer base, Location loc) throws EvalException {
if (x instanceof String) {
return fromString(x, loc, base);
} else {
if (base != 10) {
throw new EvalException(loc, "int() can't convert non-string with explicit base");
}
if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? 1 : 0;
} else if (x instanceof Integer) {
return (Integer) x;
}
throw new EvalException(
loc, Printer.format("%r is not of type string or int or bool", x));
}
}
} else {
throw new EvalException(loc,
Printer.format("%r is not of type string or int or bool", x));
}
}
};

private int fromString(Object x, Location loc, int base) throws EvalException {
String value = (String) x;
String prefix = getIntegerPrefix(value);

if (!prefix.isEmpty()) {
value = value.substring(prefix.length());
int expectedBase = intPrefixes.get(prefix);
if (base == 0) {
// Similar to Python, base 0 means "derive the base from the prefix".
base = expectedBase;
} else if (base != expectedBase) {
throw new EvalException(
loc, Printer.format("invalid literal for int() with base %d: %r", base, x));
}
}

if (base < 2 || base > 36) {
throw new EvalException(loc, "int() base must be >= 2 and <= 36");
}
try {
return Integer.parseInt(value, base);
} catch (NumberFormatException e) {
throw new EvalException(
loc, Printer.format("invalid literal for int() with base %d: %r", base, x));
}
}

private String getIntegerPrefix(String value) {
value = value.toLowerCase();
for (String prefix : intPrefixes.keySet()) {
if (value.startsWith(prefix)) {
return prefix;
}
}
return "";
}
};

@SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc =
"Creates an immutable struct using the keyword arguments as attributes. It is used to group "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1658,15 +1658,54 @@ public void testInt() throws Exception {
new BothModesTest()
.testStatement("int('1')", 1)
.testStatement("int('-1234')", -1234)
.testIfErrorContains("invalid literal for int(): \"1.5\"", "int('1.5')")
.testIfErrorContains("invalid literal for int(): \"ab\"", "int('ab')")
.testIfErrorContains("invalid literal for int() with base 10: \"1.5\"", "int('1.5')")
.testIfErrorContains("invalid literal for int() with base 10: \"ab\"", "int('ab')")
.testStatement("int(42)", 42)
.testStatement("int(-1)", -1)
.testStatement("int(True)", 1)
.testStatement("int(False)", 0)
.testIfErrorContains("None is not of type string or int or bool", "int(None)");
}

@Test
public void testIntWithBase() throws Exception {
new BothModesTest()
.testStatement("int('11', 2)", 3)
.testStatement("int('11', 9)", 10)
.testStatement("int('AF', 16)", 175)
.testStatement("int('11', 36)", 37)
.testStatement("int('az', 36)", 395);
}

@Test
public void testIntWithBase_InvalidBase() throws Exception {
new BothModesTest()
.testIfExactError("invalid literal for int() with base 3: \"123\"", "int('123', 3)")
.testIfExactError("invalid literal for int() with base 15: \"FF\"", "int('FF', 15)")
.testIfExactError("int() base must be >= 2 and <= 36", "int('123', -1)")
.testIfExactError("int() base must be >= 2 and <= 36", "int('123', 1)")
.testIfExactError("int() base must be >= 2 and <= 36", "int('123', 37)");
}

@Test
public void testIntWithBase_Prefix() throws Exception {
new BothModesTest()
.testStatement("int('0b11', 0)", 3)
.testStatement("int('0B11', 2)", 3)
.testStatement("int('0o11', 0)", 9)
.testStatement("int('0O11', 8)", 9)
.testStatement("int('0XFF', 0)", 255)
.testStatement("int('0xFF', 16)", 255)
.testIfExactError("invalid literal for int() with base 8: \"0xFF\"", "int('0xFF', 8)");
}

@Test
public void testIntWithBase_NoString() throws Exception {
new BothModesTest()
.testIfExactError("int() can't convert non-string with explicit base", "int(True, 2)")
.testIfExactError("int() can't convert non-string with explicit base", "int(1, 2)");
}

@Test
public void testStrFunction() throws Exception {
new SkylarkTest().testStatement("def foo(x): return x\nstr(foo)", "<function foo>");
Expand Down

0 comments on commit bc1ff69

Please sign in to comment.