-
-
Notifications
You must be signed in to change notification settings - Fork 925
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4017 from jruby/truffle-sprintf-update
[Truffle] Refactor sprintf parser/builder, update format nodes
- 9.4.12.0
- 9.4.11.0
- 9.4.10.0
- 9.4.9.0
- 9.4.8.0
- 9.4.7.0
- 9.4.6.0
- 9.4.5.0
- 9.4.4.0
- 9.4.3.0
- 9.4.2.0
- 9.4.1.0
- 9.4.0.0
- 9.3.15.0
- 9.3.14.0
- 9.3.13.0
- 9.3.12.0
- 9.3.11.0
- 9.3.10.0
- 9.3.9.0
- 9.3.8.0
- 9.3.7.0
- 9.3.6.0
- 9.3.5.0
- 9.3.4.0
- 9.3.3.0
- 9.3.2.0
- 9.3.1.0
- 9.3.0.0
- 9.2.21.0
- 9.2.20.1
- 9.2.20.0
- 9.2.19.0
- 9.2.18.0
- 9.2.17.0
- 9.2.16.0
- 9.2.15.0
- 9.2.14.0
- 9.2.13.0
- 9.2.12.0
- 9.2.11.1
- 9.2.11.0
- 9.2.10.0
- 9.2.9.0
- 9.2.8.0
- 9.2.7.0
- 9.2.6.0
- 9.2.5.0
- 9.2.4.1
- 9.2.4.0
- 9.2.3.0
- 9.2.2.0
- 9.2.1.0
- 9.2.0.0
- 9.1.17.0
- 9.1.16.0
- 9.1.15.0
- 9.1.14.0
- 9.1.13.0
- 9.1.12.0
- 9.1.11.0
- 9.1.10.0
- 9.1.9.0
- 9.1.8.0
- 9.1.7.0
- 9.1.6.0
- 9.1.5.0
- 9.1.4.0
- 9.1.3.0
Showing
15 changed files
with
1,225 additions
and
529 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 0 additions & 30 deletions
30
truffle/src/main/antlr4/org/jruby/truffle/core/format/printf/PrintfLexer.g4
This file was deleted.
Oops, something went wrong.
32 changes: 0 additions & 32 deletions
32
truffle/src/main/antlr4/org/jruby/truffle/core/format/printf/PrintfParser.g4
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
truffle/src/main/java/org/jruby/truffle/core/format/format/FormatCharacterNode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/* | ||
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This | ||
* code is released under a tri EPL/GPL/LGPL license. You can use it, | ||
* redistribute it and/or modify it under the terms of the: | ||
* | ||
* Eclipse Public License version 1.0 | ||
* GNU General Public License version 2 | ||
* GNU Lesser General Public License version 2.1 | ||
*/ | ||
package org.jruby.truffle.core.format.format; | ||
|
||
import com.oracle.truffle.api.CompilerDirectives; | ||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; | ||
import com.oracle.truffle.api.dsl.Cached; | ||
import com.oracle.truffle.api.dsl.NodeChild; | ||
import com.oracle.truffle.api.dsl.NodeChildren; | ||
import com.oracle.truffle.api.dsl.Specialization; | ||
import com.oracle.truffle.api.frame.VirtualFrame; | ||
import org.jruby.truffle.RubyContext; | ||
import org.jruby.truffle.core.format.FormatNode; | ||
import org.jruby.truffle.core.format.LiteralFormatNode; | ||
import org.jruby.truffle.core.format.convert.ToIntegerNode; | ||
import org.jruby.truffle.core.format.convert.ToIntegerNodeGen; | ||
import org.jruby.truffle.core.format.convert.ToStringNode; | ||
import org.jruby.truffle.core.format.convert.ToStringNodeGen; | ||
import org.jruby.truffle.core.format.exceptions.NoImplicitConversionException; | ||
import org.jruby.truffle.core.format.write.bytes.WriteByteNodeGen; | ||
import org.jruby.truffle.language.control.RaiseException; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
|
||
@NodeChildren({ | ||
@NodeChild(value = "width", type = FormatNode.class), | ||
@NodeChild(value = "value", type = FormatNode.class), | ||
}) | ||
public abstract class FormatCharacterNode extends FormatNode { | ||
|
||
private final boolean hasMinusFlag; | ||
|
||
@Child private ToIntegerNode toIntegerNode; | ||
@Child private ToStringNode toStringNode; | ||
|
||
public FormatCharacterNode(RubyContext context, boolean hasMinusFlag) { | ||
super(context); | ||
this.hasMinusFlag = hasMinusFlag; | ||
} | ||
|
||
@Specialization( | ||
guards = { | ||
"width == cachedWidth" | ||
}, | ||
limit = "getLimit()" | ||
) | ||
byte[] formatCached(VirtualFrame frame, int width, Object value, | ||
@Cached("width") int cachedWidth, | ||
@Cached("makeFormatString(width)") String cachedFormatString) { | ||
final String charString = getCharString(frame, value); | ||
return doFormat(charString, cachedFormatString); | ||
} | ||
|
||
@Specialization(contains = "formatCached") | ||
protected byte[] format(VirtualFrame frame, int width, Object value) { | ||
final String charString = getCharString(frame, value); | ||
return doFormat(charString, makeFormatString(width)); | ||
} | ||
|
||
protected String getCharString(VirtualFrame frame, Object value) { | ||
if (toStringNode == null) { | ||
CompilerDirectives.transferToInterpreterAndInvalidate(); | ||
toStringNode = insert(ToStringNodeGen.create(getContext(), | ||
false, | ||
"to_str", | ||
false, | ||
null, | ||
WriteByteNodeGen.create(getContext(), new LiteralFormatNode(getContext(), value)))); | ||
} | ||
Object toStrResult; | ||
try { | ||
toStrResult = toStringNode.executeToString(frame, value); | ||
} catch (NoImplicitConversionException e) { | ||
toStrResult = null; | ||
} | ||
|
||
final String charString; | ||
if (toStrResult == null || isNil(toStrResult)) { | ||
if (toIntegerNode == null) { | ||
CompilerDirectives.transferToInterpreterAndInvalidate(); | ||
toIntegerNode = insert(ToIntegerNodeGen.create(getContext(), null)); | ||
} | ||
final int charValue = (int) toIntegerNode.executeToInteger(frame, value); | ||
// TODO BJF check char length is > 0 | ||
charString = Character.toString((char) charValue); | ||
} else { | ||
final String resultString = new String((byte[]) toStrResult); | ||
final int size = resultString.length(); | ||
if (size > 1) { | ||
throw new RaiseException(getContext().getCoreExceptions().argumentErrorCharacterRequired(this)); | ||
} | ||
charString = resultString; | ||
} | ||
return charString; | ||
} | ||
|
||
@TruffleBoundary | ||
protected String makeFormatString(int width) { | ||
final boolean leftJustified = hasMinusFlag || width < 0; | ||
if (width < 0) { | ||
width = -width; | ||
} | ||
return "%" + (leftJustified ? "-" : "") + width + "." + width + "s"; | ||
} | ||
|
||
@TruffleBoundary | ||
private byte[] doFormat(String charString, String formatString) { | ||
return String.format(formatString, charString).getBytes(StandardCharsets.US_ASCII); | ||
} | ||
|
||
protected int getLimit() { | ||
return getContext().getOptions().PACK_CACHE; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
393 changes: 393 additions & 0 deletions
393
truffle/src/main/java/org/jruby/truffle/core/format/printf/PrintfSimpleParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,393 @@ | ||
/* | ||
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This | ||
* code is released under a tri EPL/GPL/LGPL license. You can use it, | ||
* redistribute it and/or modify it under the terms of the: | ||
* | ||
* Eclipse Public License version 1.0 | ||
* GNU General Public License version 2 | ||
* GNU Lesser General Public License version 2.1 | ||
*/ | ||
package org.jruby.truffle.core.format.printf; | ||
|
||
import org.jruby.truffle.core.format.exceptions.InvalidFormatException; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class PrintfSimpleParser { | ||
|
||
private final char[] source; | ||
|
||
public PrintfSimpleParser(char[] source) { | ||
this.source = source; | ||
} | ||
|
||
public List<SprintfConfig> parse() { | ||
List<SprintfConfig> configs = new ArrayList<>(); | ||
ArgType argType = ArgType.NONE; | ||
|
||
final int end = source.length; | ||
|
||
for (int i = 0; i < end; ) { | ||
|
||
// Add literal bytes up to the first % | ||
int literalEnd = i; | ||
for (; literalEnd < end && source[literalEnd] != '%'; literalEnd++) { | ||
} | ||
final int literalLength = literalEnd - i; | ||
if (literalLength > 0) { | ||
SprintfConfig config = new SprintfConfig(); | ||
config.setLiteral(true); | ||
final char[] literalBytes = new char[literalLength]; | ||
System.arraycopy(source, i, literalBytes, 0, literalLength); | ||
config.setLiteralBytes(charsToBytes(literalBytes)); | ||
configs.add(config); | ||
} | ||
if (literalEnd >= end) { | ||
break; // format string ends with a literal | ||
} | ||
|
||
i = literalEnd + 1; // skip first % | ||
|
||
SprintfConfig config = new SprintfConfig(); | ||
configs.add(config); | ||
|
||
boolean finished = false; | ||
boolean argTypeSet = false; | ||
|
||
while (!finished) { | ||
char p = i >= this.source.length ? '\0' : this.source[i]; | ||
|
||
switch (p) { | ||
case ' ': | ||
config.checkForFlags(); | ||
config.setHasSpace(true); | ||
i++; | ||
break; | ||
case '#': | ||
config.checkForFlags(); | ||
config.setFsharp(true); | ||
i++; | ||
break; | ||
case '+': | ||
config.checkForFlags(); | ||
config.setPlus(true); | ||
i++; | ||
break; | ||
case '-': | ||
config.checkForFlags(); | ||
config.setMinus(true); | ||
i++; | ||
break; | ||
case '0': | ||
config.checkForFlags(); | ||
config.setZero(true); | ||
i++; | ||
break; | ||
case '1': | ||
case '2': | ||
case '3': | ||
case '4': | ||
case '5': | ||
case '6': | ||
case '7': | ||
case '8': | ||
case '9': | ||
LookAheadResult r = getNum(i, end); | ||
i = r.getNextI(); | ||
if (valueAt(i) != null && valueAt(i) == '$') { | ||
if (config.getAbsoluteArgumentIndex() != null) { | ||
throw new InvalidFormatException("value given twice - " + r.getNumber() + "$"); | ||
} | ||
checkPosArg(argType, r.getNumber()); | ||
argType = ArgType.NUMBERED; | ||
argTypeSet = true; | ||
config.setAbsoluteArgumentIndex(r.getNumber()); | ||
i++; | ||
break; | ||
} | ||
|
||
config.checkForWidth(); | ||
config.setWidth(r.getNumber()); | ||
break; | ||
case '<': | ||
case '{': | ||
char term = (p == '<') ? '>' : '}'; | ||
|
||
int j = i; | ||
for (; j < end && this.source[j] != term; ) { | ||
j++; | ||
} | ||
if (j >= end) { | ||
throw new InvalidFormatException("malformed name - unmatched parenthesis"); | ||
} | ||
final int nameLength = j - (i + 1); | ||
final char[] nameBytes = new char[nameLength]; | ||
System.arraycopy(this.source, (i + 1), nameBytes, 0, nameLength); | ||
config.setNamesBytes(charsToBytes(nameBytes)); | ||
i = j + 1; | ||
checkNameArg(argType, nameBytes); | ||
argType = ArgType.NAMED; | ||
argTypeSet = true; | ||
if (term == '}') { | ||
config.setFormatType(SprintfConfig.FormatType.OTHER); | ||
config.setFormat('s'); | ||
finished = true; | ||
} | ||
break; | ||
case '*': | ||
config.checkForWidth(); | ||
|
||
LookAheadResult numberDollarWidth = getNumberDollar(i + 1, end); | ||
if (numberDollarWidth.getNumber() != null) { | ||
config.setArgWidth(true); | ||
config.setWidth(numberDollarWidth.getNumber()); | ||
checkPosArg(argType, numberDollarWidth.getNumber()); | ||
argType = ArgType.NUMBERED; | ||
i = numberDollarWidth.getNextI(); | ||
} else { | ||
checkNextArg(argType, 1); // TODO index next args | ||
argType = ArgType.UNNUMBERED; | ||
config.setWidthStar(true); | ||
i++; | ||
} | ||
break; | ||
case '.': | ||
if (config.hasPrecision()) { | ||
throw new InvalidFormatException("precision given twice"); | ||
} | ||
config.setPrecisionVisited(true); | ||
if (valueAt(i + 1) != null && valueAt(i + 1) == '*') { | ||
LookAheadResult numberDollar = getNumberDollar(i + 2, end); | ||
if (numberDollar.getNumber() != null) { | ||
config.setPrecision(numberDollar.getNumber()); | ||
config.setPrecisionArg(true); | ||
checkPosArg(argType, numberDollar.getNumber()); | ||
argType = ArgType.NUMBERED; | ||
i = numberDollar.getNextI(); | ||
} else { | ||
checkNextArg(argType, 1); // TODO idx | ||
argType = ArgType.UNNUMBERED; | ||
config.setPrecisionStar(true); | ||
i += 2; | ||
} | ||
break; | ||
} | ||
|
||
LookAheadResult re = getNum(i + 1, end); | ||
config.setPrecision(re.getNumber()); | ||
i = re.getNextI(); | ||
break; | ||
case '\n': | ||
case '\0': | ||
i--; | ||
case '%': | ||
if (config.hasFlags()) { | ||
throw new InvalidFormatException("invalid format character - %"); | ||
} | ||
config.setLiteral(true); | ||
byte[] literal = {(byte) '%'}; | ||
config.setLiteralBytes(literal); | ||
i++; | ||
finished = true; | ||
break; | ||
case 'c': | ||
config.setFormatType(SprintfConfig.FormatType.OTHER); | ||
config.setFormat(p); | ||
i++; | ||
if (!argTypeSet) { | ||
checkNextArg(argType, 1); | ||
argType = ArgType.UNNUMBERED; | ||
} | ||
finished = true; | ||
break; | ||
case 's': | ||
case 'p': | ||
config.setFormatType(SprintfConfig.FormatType.OTHER); | ||
config.setFormat(p); | ||
i++; | ||
if (!argTypeSet) { // Speculative | ||
checkNextArg(argType, 1); | ||
argType = ArgType.UNNUMBERED; | ||
} | ||
finished = true; | ||
break; | ||
case 'd': | ||
case 'i': | ||
case 'o': | ||
case 'x': | ||
case 'X': | ||
case 'b': | ||
case 'B': | ||
case 'u': | ||
if (!argTypeSet) { | ||
checkNextArg(argType, 1); // TODO idx correctly | ||
argType = ArgType.UNNUMBERED; | ||
} | ||
config.setFormatType(SprintfConfig.FormatType.INTEGER); | ||
config.setFormat(p); | ||
finished = true; | ||
i++; | ||
break; | ||
case 'g': | ||
case 'G': | ||
case 'e': | ||
case 'E': | ||
case 'a': | ||
case 'A': | ||
case 'f': | ||
if (!argTypeSet) { | ||
checkNextArg(argType, 1); | ||
argType = ArgType.UNNUMBERED; | ||
} | ||
config.setFormatType(SprintfConfig.FormatType.FLOAT); | ||
config.setFormat(p); | ||
finished = true; | ||
i++; | ||
break; | ||
default: | ||
throw new InvalidFormatException("malformed format string - %" + p); | ||
} | ||
} | ||
} | ||
return configs; | ||
} | ||
|
||
|
||
private static void checkNextArg(ArgType argType, int nextArgumentIndex) { | ||
switch (argType) { | ||
case NUMBERED: | ||
throw new InvalidFormatException("unnumbered(" + nextArgumentIndex + ") mixed with numbered"); | ||
case NAMED: | ||
throw new InvalidFormatException("unnumbered(" + nextArgumentIndex + ") mixed with named"); | ||
} | ||
} | ||
|
||
private static void checkPosArg(ArgType posarg, int nextArgumentIndex) { | ||
if (posarg == ArgType.UNNUMBERED) { | ||
throw new InvalidFormatException("numbered(" + nextArgumentIndex + ") after unnumbered(" + posarg + ")"); | ||
} | ||
if (posarg == ArgType.NAMED) { | ||
throw new InvalidFormatException("numbered(" + nextArgumentIndex + ") after named"); | ||
} | ||
if (nextArgumentIndex < 1) { | ||
throw new InvalidFormatException("invalid index - " + nextArgumentIndex + "$"); | ||
} | ||
} | ||
|
||
private static void checkNameArg(ArgType argType, char[] name) { | ||
if (argType == ArgType.UNNUMBERED) { | ||
throw new InvalidFormatException("named" + new String(name) + " after unnumbered(%d)"); | ||
} | ||
if (argType == ArgType.NUMBERED) { | ||
throw new InvalidFormatException("named" + new String(name) + " after numbered"); | ||
} | ||
} | ||
|
||
private enum ArgType { | ||
NONE, | ||
NUMBERED, | ||
UNNUMBERED, | ||
NAMED | ||
} | ||
|
||
private void checkPosArg(int relativeArgumentIndex, int absoluteArgumentIndex) { | ||
if (relativeArgumentIndex > 0) { | ||
throw new InvalidFormatException("numbered(" + absoluteArgumentIndex + ") after unnumbered(" + relativeArgumentIndex + ")"); | ||
} | ||
if (relativeArgumentIndex == -2) { | ||
throw new InvalidFormatException("numbered(" + absoluteArgumentIndex + ") after named"); | ||
} | ||
if (absoluteArgumentIndex < 1) { | ||
throw new InvalidFormatException("invalid index - " + absoluteArgumentIndex + "$"); | ||
} | ||
} | ||
|
||
public LookAheadResult getNum(int startI, int end) { | ||
StringBuilder sb = new StringBuilder(); | ||
|
||
int moreChars = 0; | ||
for (int i = startI; i < end; i++) { | ||
char nextChar = source[i]; | ||
if (!isDigit(nextChar)) { | ||
break; | ||
} else { | ||
sb.append(nextChar); | ||
moreChars += 1; | ||
} | ||
} | ||
|
||
final int nextI = startI + moreChars; | ||
|
||
if (nextI >= end) { | ||
throw new InvalidFormatException("malformed format string - %%*[0-9]"); | ||
} | ||
|
||
Integer result; | ||
if (sb.length() > 0) { | ||
result = Integer.parseInt(sb.toString()); | ||
} else { | ||
result = null; | ||
} | ||
return new LookAheadResult(result, nextI); | ||
} | ||
|
||
public LookAheadResult getNumberDollar(int startI, int end) { | ||
LookAheadResult lar = getNum(startI, end); | ||
Integer result = null; | ||
int newI = startI; | ||
if (lar.getNumber() != null) { | ||
final int nextI = lar.getNextI(); | ||
if (valueAt(nextI) != null && valueAt(nextI) == '$') { | ||
result = lar.getNumber(); | ||
newI = nextI + 1; | ||
if (result < 1) { | ||
throw new InvalidFormatException("invalid index - " + result + "$"); | ||
} | ||
} | ||
} | ||
return new LookAheadResult(result, newI); | ||
} | ||
|
||
public static class LookAheadResult { | ||
private Integer number; | ||
private int nextI; | ||
|
||
public LookAheadResult(Integer number, int nextI) { | ||
this.number = number; | ||
this.nextI = nextI; | ||
} | ||
|
||
public Integer getNumber() { | ||
return number; | ||
} | ||
|
||
public int getNextI() { | ||
return nextI; | ||
} | ||
} | ||
|
||
public static boolean isDigit(char c) { | ||
return c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9'; | ||
} | ||
|
||
public Character valueAt(int index) { | ||
assert index >= 0; | ||
if (index < this.source.length) { | ||
return this.source[index]; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
private static byte[] charsToBytes(char[] chars) { | ||
final byte[] bytes = new byte[chars.length]; | ||
|
||
for (int n = 0; n < chars.length; n++) { | ||
bytes[n] = (byte) chars[n]; | ||
} | ||
|
||
return bytes; | ||
} | ||
|
||
} |
202 changes: 202 additions & 0 deletions
202
truffle/src/main/java/org/jruby/truffle/core/format/printf/PrintfSimpleTreeBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
/* | ||
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This | ||
* code is released under a tri EPL/GPL/LGPL license. You can use it, | ||
* redistribute it and/or modify it under the terms of the: | ||
* | ||
* Eclipse Public License version 1.0 | ||
* GNU General Public License version 2 | ||
* GNU Lesser General Public License version 2.1 | ||
*/ | ||
package org.jruby.truffle.core.format.printf; | ||
|
||
import com.oracle.truffle.api.object.DynamicObject; | ||
import org.jcodings.specific.USASCIIEncoding; | ||
import org.jruby.truffle.RubyContext; | ||
import org.jruby.truffle.core.format.FormatNode; | ||
import org.jruby.truffle.core.format.LiteralFormatNode; | ||
import org.jruby.truffle.core.format.control.SequenceNode; | ||
import org.jruby.truffle.core.format.convert.ToDoubleWithCoercionNodeGen; | ||
import org.jruby.truffle.core.format.convert.ToIntegerNodeGen; | ||
import org.jruby.truffle.core.format.convert.ToStringNodeGen; | ||
import org.jruby.truffle.core.format.format.FormatCharacterNodeGen; | ||
import org.jruby.truffle.core.format.format.FormatFloatHumanReadableNodeGen; | ||
import org.jruby.truffle.core.format.format.FormatFloatNodeGen; | ||
import org.jruby.truffle.core.format.format.FormatIntegerBinaryNodeGen; | ||
import org.jruby.truffle.core.format.format.FormatIntegerNodeGen; | ||
import org.jruby.truffle.core.format.read.SourceNode; | ||
import org.jruby.truffle.core.format.read.array.ReadArgumentIndexValueNodeGen; | ||
import org.jruby.truffle.core.format.read.array.ReadHashValueNodeGen; | ||
import org.jruby.truffle.core.format.read.array.ReadIntegerNodeGen; | ||
import org.jruby.truffle.core.format.read.array.ReadStringNodeGen; | ||
import org.jruby.truffle.core.format.read.array.ReadValueNodeGen; | ||
import org.jruby.truffle.core.format.write.bytes.WriteBytesNodeGen; | ||
import org.jruby.truffle.core.format.write.bytes.WritePaddedBytesNodeGen; | ||
import org.jruby.truffle.core.rope.CodeRange; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class PrintfSimpleTreeBuilder { | ||
|
||
private final RubyContext context; | ||
private final List<FormatNode> sequence = new ArrayList<>(); | ||
private final List<SprintfConfig> configs; | ||
|
||
public static int DEFAULT = -1; | ||
|
||
private static final byte[] EMPTY_BYTES = new byte[]{}; | ||
|
||
public PrintfSimpleTreeBuilder(RubyContext context, List<SprintfConfig> configs) { | ||
this.context = context; | ||
this.configs = configs; | ||
} | ||
|
||
private void buildTree() { | ||
for (SprintfConfig config : configs) { | ||
final FormatNode node; | ||
if (config.isLiteral()) { | ||
node = WriteBytesNodeGen.create(context, new LiteralFormatNode(context, config.getLiteralBytes())); | ||
} else { | ||
final FormatNode valueNode; | ||
|
||
if (config.getNamesBytes() != null) { | ||
final DynamicObject key = context.getSymbolTable().getSymbol(context.getRopeTable().getRope(config.getNamesBytes(), USASCIIEncoding.INSTANCE, CodeRange.CR_7BIT)); | ||
valueNode = ReadHashValueNodeGen.create(context, key, new SourceNode()); | ||
} else if (config.getAbsoluteArgumentIndex() != null) { | ||
valueNode = ReadArgumentIndexValueNodeGen.create(context, config.getAbsoluteArgumentIndex(), new SourceNode()); | ||
} else { | ||
valueNode = ReadValueNodeGen.create(context, new SourceNode()); | ||
} | ||
|
||
final FormatNode widthNode; | ||
if (config.isWidthStar()) { | ||
widthNode = ReadIntegerNodeGen.create(context, new SourceNode()); | ||
} else if (config.isArgWidth()){ | ||
widthNode = ReadArgumentIndexValueNodeGen.create(context, config.getWidth(), new SourceNode()); | ||
} else { | ||
widthNode = new LiteralFormatNode(context, config.getWidth() == null ? -1 : config.getWidth()); | ||
} | ||
|
||
final FormatNode precisionNode; | ||
if(config.isPrecisionStar()){ | ||
precisionNode = ReadIntegerNodeGen.create(context, new SourceNode()); | ||
} else if(config.isPrecisionArg()){ | ||
precisionNode = ReadArgumentIndexValueNodeGen.create(context, config.getPrecision(), new SourceNode()); | ||
} else { | ||
precisionNode = new LiteralFormatNode(context, config.getPrecision() == null ? -1 : config.getPrecision()); | ||
} | ||
|
||
|
||
switch (config.getFormatType()){ | ||
case INTEGER: | ||
final char format; | ||
switch (config.getFormat()) { | ||
case 'b': | ||
case 'B': | ||
format = config.getFormat(); | ||
break; | ||
case 'd': | ||
case 'i': | ||
case 'u': | ||
format = 'd'; | ||
break; | ||
case 'o': | ||
format = 'o'; | ||
break; | ||
case 'x': | ||
case 'X': | ||
format = config.getFormat(); | ||
break; | ||
default: | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
if(config.getFormat() == 'b' || config.getFormat() == 'B'){ | ||
node = WriteBytesNodeGen.create(context, | ||
FormatIntegerBinaryNodeGen.create(context, format, | ||
config.isPlus(), config.isFsharp(), | ||
config.isMinus(), | ||
config.isHasSpace(), | ||
config.isZero(), | ||
widthNode, | ||
precisionNode, | ||
ToIntegerNodeGen.create(context, valueNode))); | ||
} else { | ||
node = WriteBytesNodeGen.create(context, | ||
FormatIntegerNodeGen.create(context, format, config.isHasSpace(), config.isZero(), config.isPlus(), config.isMinus(), config.isFsharp(), | ||
widthNode, | ||
precisionNode, | ||
ToIntegerNodeGen.create(context, valueNode))); | ||
} | ||
break; | ||
case FLOAT: | ||
switch (config.getFormat()){ | ||
case 'f': | ||
case 'e': | ||
case 'E': | ||
node = WriteBytesNodeGen.create(context, | ||
FormatFloatNodeGen.create(context, | ||
config.getFormat(), config.isHasSpace(), config.isZero(), config.isPlus(), config.isMinus(), | ||
widthNode, | ||
precisionNode, | ||
ToDoubleWithCoercionNodeGen.create(context, | ||
valueNode))); | ||
break; | ||
case 'g': | ||
case 'G': | ||
node = WriteBytesNodeGen.create(context, | ||
FormatFloatHumanReadableNodeGen.create(context, | ||
ToDoubleWithCoercionNodeGen.create(context, | ||
valueNode))); | ||
break; | ||
default: | ||
throw new UnsupportedOperationException(); | ||
} | ||
break; | ||
case OTHER: | ||
switch (config.getFormat()){ | ||
case 'c': | ||
node = WriteBytesNodeGen.create(context, | ||
FormatCharacterNodeGen.create(context, config.isMinus(), widthNode, | ||
valueNode)); | ||
break; | ||
case 's': | ||
case 'p': | ||
final String conversionMethodName = config.getFormat() == 's' ? "to_s" : "inspect"; | ||
final FormatNode conversionNode; | ||
|
||
if(config.getAbsoluteArgumentIndex() == null && config.getNamesBytes() == null) { | ||
conversionNode = ReadStringNodeGen.create(context, true, conversionMethodName, false, EMPTY_BYTES, new SourceNode()); | ||
} else { | ||
conversionNode = ToStringNodeGen.create(context, true, conversionMethodName, false, EMPTY_BYTES, valueNode); | ||
} | ||
|
||
if (config.getWidth() != null || config.isWidthStar()) { | ||
node = WritePaddedBytesNodeGen.create(context, config.isMinus(), widthNode, conversionNode); | ||
} else { | ||
node = WriteBytesNodeGen.create(context, conversionNode); | ||
} | ||
break; | ||
default: | ||
throw new UnsupportedOperationException(); | ||
} | ||
break; | ||
default: | ||
throw new UnsupportedOperationException("unsupported type: " + config.getFormatType().toString()); | ||
} | ||
|
||
} | ||
sequence.add(node); | ||
} | ||
|
||
|
||
} | ||
|
||
|
||
|
||
public FormatNode getNode() { | ||
buildTree(); | ||
return new SequenceNode(context, sequence.toArray(new FormatNode[sequence.size()])); | ||
} | ||
|
||
} |
276 changes: 0 additions & 276 deletions
276
truffle/src/main/java/org/jruby/truffle/core/format/printf/PrintfTreeBuilder.java
This file was deleted.
Oops, something went wrong.
220 changes: 220 additions & 0 deletions
220
truffle/src/main/java/org/jruby/truffle/core/format/printf/SprintfConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
/* | ||
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This | ||
* code is released under a tri EPL/GPL/LGPL license. You can use it, | ||
* redistribute it and/or modify it under the terms of the: | ||
* | ||
* Eclipse Public License version 1.0 | ||
* GNU General Public License version 2 | ||
* GNU Lesser General Public License version 2.1 | ||
*/ | ||
package org.jruby.truffle.core.format.printf; | ||
|
||
import org.jruby.truffle.core.format.exceptions.InvalidFormatException; | ||
|
||
public class SprintfConfig { | ||
|
||
public enum FormatType { | ||
INTEGER, FLOAT, OTHER | ||
} | ||
|
||
private boolean literal = false; | ||
private byte[] literalBytes; | ||
|
||
private byte[] namesBytes; | ||
private boolean argWidth = false; | ||
|
||
private Integer absoluteArgumentIndex; | ||
private Integer precision; | ||
private boolean precisionArg = false; | ||
private boolean precisionVisited = false; | ||
private Integer width; | ||
private boolean hasSpace = false; | ||
private boolean fsharp = false; // # | ||
private boolean plus = false; | ||
private boolean minus = false; | ||
private boolean zero = false; | ||
private boolean widthStar = false; | ||
private boolean precisionStar = false; | ||
private char format; | ||
private FormatType formatType; | ||
|
||
|
||
public void checkForFlags() { | ||
if (hasWidth()) { | ||
throw new InvalidFormatException("flag after width"); | ||
} | ||
if (hasPrecision()) { | ||
throw new InvalidFormatException("flag after precision"); | ||
} | ||
} | ||
|
||
public void checkForWidth() { | ||
if (hasWidth()) { | ||
throw new InvalidFormatException("width given twice"); | ||
} | ||
if (hasPrecision()) { | ||
throw new InvalidFormatException("width after precision"); | ||
} | ||
} | ||
|
||
public boolean isHasSpace() { | ||
return hasSpace; | ||
} | ||
|
||
public void setHasSpace(boolean hasSpace) { | ||
this.hasSpace = hasSpace; | ||
} | ||
|
||
public Integer getPrecision() { | ||
return precision; | ||
} | ||
|
||
public void setPrecision(int precision) { | ||
this.precision = precision; | ||
} | ||
|
||
public void setPrecision(Integer precision) { | ||
this.precision = precision; | ||
} | ||
|
||
public Integer getWidth() { | ||
return width; | ||
} | ||
|
||
public void setWidth(Integer width) { | ||
this.width = width; | ||
} | ||
|
||
public boolean isFsharp() { | ||
return fsharp; | ||
} | ||
|
||
public void setFsharp(boolean fsharp) { | ||
this.fsharp = fsharp; | ||
} | ||
|
||
public boolean isPlus() { | ||
return plus; | ||
} | ||
|
||
public void setPlus(boolean plus) { | ||
this.plus = plus; | ||
} | ||
|
||
public boolean isMinus() { | ||
return minus; | ||
} | ||
|
||
public void setMinus(boolean minus) { | ||
this.minus = minus; | ||
} | ||
|
||
public boolean isZero() { | ||
return zero; | ||
} | ||
|
||
public void setZero(boolean zero) { | ||
this.zero = zero; | ||
} | ||
|
||
public FormatType getFormatType() { | ||
return formatType; | ||
} | ||
|
||
public void setFormatType(FormatType formatType) { | ||
this.formatType = formatType; | ||
} | ||
|
||
public char getFormat() { | ||
return format; | ||
} | ||
|
||
public void setFormat(char format) { | ||
this.format = format; | ||
} | ||
|
||
public boolean isWidthStar() { | ||
return widthStar; | ||
} | ||
|
||
public void setWidthStar(boolean widthStar) { | ||
this.widthStar = widthStar; | ||
} | ||
|
||
public boolean isPrecisionStar() { | ||
return precisionStar; | ||
} | ||
|
||
public void setPrecisionStar(boolean precisionStar) { | ||
this.precisionStar = precisionStar; | ||
} | ||
|
||
public boolean hasPrecision() { | ||
return precision != null || precisionStar || precisionVisited; | ||
} | ||
|
||
public boolean hasWidth() { | ||
return width != null || widthStar; | ||
} | ||
|
||
public boolean isLiteral() { | ||
return literal; | ||
} | ||
|
||
public void setLiteral(boolean literal) { | ||
this.literal = literal; | ||
} | ||
|
||
public byte[] getLiteralBytes() { | ||
return literalBytes; | ||
} | ||
|
||
public void setLiteralBytes(byte[] literalBytes) { | ||
this.literalBytes = literalBytes; | ||
} | ||
|
||
public Integer getAbsoluteArgumentIndex() { | ||
return absoluteArgumentIndex; | ||
} | ||
|
||
public void setAbsoluteArgumentIndex(Integer absoluteArgumentIndex) { | ||
this.absoluteArgumentIndex = absoluteArgumentIndex; | ||
} | ||
|
||
public boolean isArgWidth() { | ||
return argWidth; | ||
} | ||
|
||
public void setArgWidth(boolean argWidth) { | ||
this.argWidth = argWidth; | ||
} | ||
|
||
|
||
public boolean isPrecisionVisited() { | ||
return precisionVisited; | ||
} | ||
|
||
public void setPrecisionVisited(boolean precisionVisited) { | ||
this.precisionVisited = precisionVisited; | ||
} | ||
|
||
public byte[] getNamesBytes() { | ||
return namesBytes; | ||
} | ||
|
||
public void setNamesBytes(byte[] namesBytes) { | ||
this.namesBytes = namesBytes; | ||
} | ||
|
||
public boolean isPrecisionArg() { | ||
return precisionArg; | ||
} | ||
|
||
public void setPrecisionArg(boolean precisionArg) { | ||
this.precisionArg = precisionArg; | ||
} | ||
|
||
public boolean hasFlags() { | ||
return literal || precision != null || precisionVisited || width != null || hasSpace || fsharp || plus || minus || zero || precisionStar || widthStar || formatType != null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters