From 3da92ab80d4dacd38df41ff76bcc345cb0fda1ce Mon Sep 17 00:00:00 2001 From: NebelNidas <48808497+NebelNidas@users.noreply.github.com> Date: Sun, 14 Apr 2024 14:07:37 +0200 Subject: [PATCH 1/4] Fix and update Javadocs (#98) --- .../mappingio/format/MappingFormat.java | 144 ++++-------------- .../format/enigma/EnigmaDirReader.java | 2 +- .../format/enigma/EnigmaDirWriter.java | 2 +- .../format/simple/RecafSimpleFileReader.java | 2 +- .../format/simple/RecafSimpleFileWriter.java | 2 +- .../mappingio/format/srg/JamFileWriter.java | 3 +- .../mappingio/format/srg/SrgFileWriter.java | 5 +- .../format/tiny/Tiny1FileReader.java | 2 +- .../format/tiny/Tiny1FileWriter.java | 2 +- .../format/tiny/Tiny2FileReader.java | 2 +- .../format/tiny/Tiny2FileWriter.java | 2 +- 11 files changed, 45 insertions(+), 123 deletions(-) diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java index 4ecf455a..70c2677b 100644 --- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java +++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java @@ -21,122 +21,15 @@ /** * Represents a supported mapping format. Every format can be assumed to have an associated reader available. * - *
Feature comparison table: - *
Format | - *Namespaces | - *Field descriptors | - *Comments | - *Parameters | - *Local variables | - *Metadata | - *
---|---|---|---|---|---|---|
Tiny v1 | - *✔ | - *src | - *- | - *- | - *- | - *✔ (Currently limited support) | - *
Tiny v2 | - *✔ | - *src | - *✔ | - *lvIdx & srcName | - *lvIdx, lvtIdx, startOpIdx & srcName | - *✔ | - *
Enigma | - *- | - *src | - *✔ | - *lvIdx | - *- | - *- | - *
SRG | - *- | - *- | - *- | - *- | - *- | - *- | - *
XSRG | - *- | - *src & dst | - *- | - *- | - *- | - *- | - *
JAM | - *- | - *src | - *- | - *argPos | - *- | - *- | - *
CSRG/TSRG | - *- | - *- | - *- | - *- | - *- | - *- | - *
TSRG2 | - *✔ | - *src | - *- | - *lvIdx & srcName | - *- | - *- | - *
ProGuard | - *- | - *src | - *- | - *- | - *- | - *- | - *
Recaf Simple | - *- | - *src & dst | - *- | - *- | - *- | - *- | - *
JOBF | - *- | - *src | - *- | - *- | - *- | - *- | - *
A feature comparison table can be found here. */ -// Format order is determined by importance to Fabric tooling, format family and release order therein. +// Format order is determined by importance to Fabric tooling, format family and release order therein (synced with the table linked above). public enum MappingFormat { /** * The {@code Tiny} mapping format, as specified here. + * + *
Crashes if a second visit pass is requested without * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand. diff --git a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirWriter.java b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirWriter.java index 4a5aa2a6..48709835 100644 --- a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirWriter.java +++ b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirWriter.java @@ -32,7 +32,7 @@ import net.fabricmc.mappingio.format.MappingFormat; /** - * {@linkplain MappingFormat#ENIGMA_DIRECTORY Enigma directory} writer. + * {@linkplain MappingFormat#ENIGMA_DIR Enigma directory} writer. */ public final class EnigmaDirWriter extends EnigmaWriterBase { public EnigmaDirWriter(Path dir, boolean deleteExistingFiles) throws IOException { diff --git a/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java index 4bff3ee1..4d4c3c59 100644 --- a/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java +++ b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java @@ -31,7 +31,7 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree; /** - * {@linkplain MappingFormat#RECAF_SIMPLE Recaf Simple file} reader. + * {@linkplain MappingFormat#RECAF_SIMPLE_FILE Recaf Simple file} reader. */ public final class RecafSimpleFileReader { private RecafSimpleFileReader() { diff --git a/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileWriter.java index 61f28b21..47e16586 100644 --- a/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileWriter.java +++ b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileWriter.java @@ -28,7 +28,7 @@ import net.fabricmc.mappingio.format.MappingFormat; /** - * {@linkplain MappingFormat#RECAF_SIMPLE Recaf Simple file} writer. + * {@linkplain MappingFormat#RECAF_SIMPLE_FILE Recaf Simple file} writer. */ public final class RecafSimpleFileWriter implements MappingWriter { public RecafSimpleFileWriter(Writer writer) { diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java index d9c993d9..3fb20cbc 100644 --- a/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java +++ b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java @@ -27,9 +27,10 @@ import net.fabricmc.mappingio.MappedElementKind; import net.fabricmc.mappingio.MappingFlag; import net.fabricmc.mappingio.MappingWriter; +import net.fabricmc.mappingio.format.MappingFormat; /** - * {@linkplain net.fabricmc.mappingio.format.MappingFormat#JAM_FILE JAM file} writer. + * {@linkplain MappingFormat#JAM_FILE JAM file} writer. */ public final class JamFileWriter implements MappingWriter { public JamFileWriter(Writer writer) { diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java index b74db414..377d3876 100644 --- a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java +++ b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java @@ -27,10 +27,11 @@ import net.fabricmc.mappingio.MappedElementKind; import net.fabricmc.mappingio.MappingFlag; import net.fabricmc.mappingio.MappingWriter; +import net.fabricmc.mappingio.format.MappingFormat; /** - * {@linkplain net.fabricmc.mappingio.format.MappingFormat#SRG_FILE SRG file} and - * {@linkplain net.fabricmc.mappingio.format.MappingFormat#XSRG_FILE XSRG file} writer. + * {@linkplain MappingFormat#SRG_FILE SRG file} and + * {@linkplain MappingFormat#XSRG_FILE XSRG file} writer. */ public final class SrgFileWriter implements MappingWriter { public SrgFileWriter(Writer writer, boolean xsrg) { diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java index f74f7f7b..522dce65 100644 --- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java +++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java @@ -31,7 +31,7 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree; /** - * {@linkplain MappingFormat#TINY_1 Tiny v1 file} reader. + * {@linkplain MappingFormat#TINY_FILE Tiny v1 file} reader. * *
Crashes if a second visit pass is requested without * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand. diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java index f419e3ef..511afaa2 100644 --- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java +++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java @@ -31,7 +31,7 @@ import net.fabricmc.mappingio.format.MappingFormat; /** - * {@linkplain MappingFormat#TINY_1 Tiny v1 file} writer. + * {@linkplain MappingFormat#TINY_FILE Tiny v1 file} writer. */ public final class Tiny1FileWriter implements MappingWriter { public Tiny1FileWriter(Writer writer) { diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java index be4b408b..ff3687c2 100644 --- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java +++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java @@ -28,7 +28,7 @@ import net.fabricmc.mappingio.format.MappingFormat; /** - * {@linkplain MappingFormat#TINY_2 Tiny v2 file} reader. + * {@linkplain MappingFormat#TINY_2_FILE Tiny v2 file} reader. * *
Crashes if a second visit pass is requested without
* {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand.
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
index f2b5b017..6b049925 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
@@ -31,7 +31,7 @@
import net.fabricmc.mappingio.format.MappingFormat;
/**
- * {@linkplain MappingFormat#TINY_2 Tiny v2 file} writer.
+ * {@linkplain MappingFormat#TINY_2_FILE Tiny v2 file} writer.
*/
public final class Tiny2FileWriter implements MappingWriter {
public Tiny2FileWriter(Writer writer, boolean escapeNames) {
From a182551c2cabb7266dfdd0cba20ef5598d08e983 Mon Sep 17 00:00:00 2001
From: NebelNidas <48808497+NebelNidas@users.noreply.github.com>
Date: Sun, 14 Apr 2024 14:07:57 +0200
Subject: [PATCH 2/4] Fix CSRG and JAM writers skipping elements whose parents
have incomplete destination names (#97)
* Fix CSRG and JAM writer bugs
* Add changes to changelog
* Reword changelog entry
---
CHANGELOG.md | 1 +
.../mappingio/format/srg/CsrgFileWriter.java | 30 +++++++++----------
.../mappingio/format/srg/JamFileWriter.java | 4 ++-
.../mappingio/SubsetAssertingVisitor.java | 10 +++----
.../resources/read/valid-with-holes/jam.jam | 2 ++
5 files changed, 26 insertions(+), 21 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3b43ede..bc3965c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- Fixed CSRG and JAM writers sometimes skipping elements whose parents have incomplete destination names
## [0.6.0] - 2024-4-12
- Added CSRG writer
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java
index a434d4cf..7bd17a82 100644
--- a/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java
@@ -93,27 +93,27 @@ public void visitDstName(MappedElementKind targetKind, int namespace, String nam
@Override
public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
- if (dstName == null) return false;
+ if (dstName != null) {
+ write(classSrcName);
- write(classSrcName);
+ if (targetKind != MappedElementKind.CLASS) {
+ writeSpace();
+ write(memberSrcName);
- if (targetKind != MappedElementKind.CLASS) {
- writeSpace();
- write(memberSrcName);
+ if (targetKind == MappedElementKind.METHOD) {
+ writeSpace();
+ write(methodSrcDesc);
+ }
- if (targetKind == MappedElementKind.METHOD) {
- writeSpace();
- write(methodSrcDesc);
+ memberSrcName = methodSrcDesc = null;
}
- memberSrcName = methodSrcDesc = null;
- }
-
- writeSpace();
- write(dstName);
- writeLn();
+ writeSpace();
+ write(dstName);
+ writeLn();
- dstName = null;
+ dstName = null;
+ }
return targetKind == MappedElementKind.CLASS; // only members are supported, skip anything but class contents
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java
index 3fb20cbc..81d6861a 100644
--- a/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java
@@ -125,7 +125,9 @@ public boolean visitElementContent(MappedElementKind targetKind) throws IOExcept
} else if (targetKind == MappedElementKind.FIELD
|| (isMethod = targetKind == MappedElementKind.METHOD)
|| (isArg = targetKind == MappedElementKind.METHOD_ARG)) {
- if (classOnlyPass || memberSrcDesc == null || memberDstName == null) {
+ if (classOnlyPass) {
+ return false;
+ } else if (memberSrcDesc == null || (!isArg && memberDstName == null)) {
return isMethod;
}
diff --git a/src/test/java/net/fabricmc/mappingio/SubsetAssertingVisitor.java b/src/test/java/net/fabricmc/mappingio/SubsetAssertingVisitor.java
index 907bc033..074760e7 100644
--- a/src/test/java/net/fabricmc/mappingio/SubsetAssertingVisitor.java
+++ b/src/test/java/net/fabricmc/mappingio/SubsetAssertingVisitor.java
@@ -80,7 +80,7 @@ public boolean visitClass(String srcName, String[] dstNames) throws IOException
if (supCls == null) {
String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]};
- if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false;
+ if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return true;
throw new RuntimeException("SubTree class not contained in SupTree: " + srcName);
}
@@ -111,7 +111,7 @@ public boolean visitField(String srcClsName, String srcName, String srcDesc,
if (supFld == null) {
String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]};
- if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false;
+ if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return true;
throw new RuntimeException("SubTree field not contained in SupTree: " + srcName);
}
@@ -149,7 +149,7 @@ public boolean visitMethod(String srcClsName, String srcName, String srcDesc,
if (supMth == null) {
String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]};
- if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false;
+ if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return true;
throw new RuntimeException("SubTree method not contained in SupTree: " + srcName);
}
@@ -187,7 +187,7 @@ public boolean visitMethodArg(String srcClsName, String srcMethodName, String sr
if (supArg == null) {
String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]};
- if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false;
+ if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return true;
throw new RuntimeException("SubTree arg not contained in SupTree: " + srcName);
}
@@ -221,7 +221,7 @@ public boolean visitMethodVar(String srcClsName, String srcMethodName, String sr
if (supVar == null) {
String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]};
- if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false;
+ if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return true;
throw new RuntimeException("SubTree var not contained in SupTree: " + srcName);
}
diff --git a/src/test/resources/read/valid-with-holes/jam.jam b/src/test/resources/read/valid-with-holes/jam.jam
index bd2f77fd..47c37514 100644
--- a/src/test/resources/read/valid-with-holes/jam.jam
+++ b/src/test/resources/read/valid-with-holes/jam.jam
@@ -8,3 +8,5 @@ FD class_32 field_2 I field2Ns0Rename
FD class_32 field_5 I field5Ns0Rename
MD class_32 method_2 ()I method2Ns0Rename
MD class_32 method_5 ()I method5Ns0Rename
+MP class_32 method_7 ()I 4 param3Ns0Rename
+MP class_32 method_7 ()I 8 param5Ns0Rename
From dcf8fbd41c6d1da9a5267108b2895bf07ca0dd6f Mon Sep 17 00:00:00 2001
From: modmuss The reader will point to the next column or end of line if successful, otherwise remains unchanged.
*
- * @param expect Content to expect.
- * @return {@code true} if the column was read and had the expected content, false otherwise.
+ * @param expected Content to expect.
+ * @return {@code true} if the column was read and had the expected content, {@code false} otherwise.
*/
- public boolean nextCol(String expect) throws IOException {
- if (eol) return false;
-
- int len = expect.length();
- if (!fillBuffer(len)) return false;
-
- for (int i = 0; i < len; i++) {
- if (buffer[bufferPos + i] != expect.charAt(i)) return false; // read failed, not all of expect available
- }
-
- char trailing = 0;
-
- if (fillBuffer(len + 1) // not eof
- && (trailing = buffer[bufferPos + len]) != columnSeparator // not end of column
- && trailing != '\n' // not end of line
- && trailing != '\r') {
- return false; // read failed, column contains data beyond expect
- }
-
- // successful read
-
- bufferPos += expect.length();
-
- // seek to the start of the next column
- if (trailing == columnSeparator) {
- bufferPos++;
- } else {
- eol = true;
- }
-
- return true;
+ public boolean nextCol(String expected) throws IOException {
+ return read(false, false, true, expected) != noMatch;
}
/**
* Read and consume a column without unescaping.
+ *
+ * @return {@code null} if nothing has been read (first char was EOL), otherwise the read string (may be empty).
*/
@Nullable
public String nextCol() throws IOException {
@@ -91,36 +65,80 @@ public String nextCol() throws IOException {
}
/**
- * Read and consume a column with unescaping.
+ * Read and consume a column, and unescape it if requested.
+ *
+ * @return {@code null} if nothing has been read (first char was EOL), otherwise the read string (may be empty).
*/
@Nullable
- public String nextEscapedCol() throws IOException {
- return nextCol(true);
+ public String nextCol(boolean unescape) throws IOException {
+ return read(unescape, true, true, null);
}
/**
- * Read and consume a column and unescape it if requested.
+ * Read a column without consuming, and unescape if requested.
+ * Since it doesn't consume, it won't (un)mark BOF, EOL or EOF.
+ *
+ * @return {@code null} if nothing has been read (first char was EOL), otherwise the read string (may be empty).
*/
@Nullable
- public String nextCol(boolean unescape) throws IOException {
- if (eol) return null;
+ public String peekCol(boolean unescape) throws IOException {
+ return read(unescape, false, true, null);
+ }
+
+ /**
+ * @param unescape Whether to unescape the read string.
+ * @param consume Whether to advance the bufferPos.
+ * @param stopAtNextCol Whether to only read one column.
+ * @param expected If not {@code null}, the read string must match this exactly, otherwise we early-exit with {@link #noMatch}. Always consumes if matched.
+ *
+ * @return {@code null} if nothing has been read (first char was EOL), otherwise the read string (may be empty).
+ * If {@code expected} is not {@code null}, it will be returned if matched, otherwise {@link #noMatch}.
+ */
+ @Nullable
+ private String read(boolean unescape, boolean consume, boolean stopAtNextCol, @Nullable String expected) throws IOException {
+ if (eol) return expected == null ? null : noMatch;
+
+ int expectedLength = expected != null ? expected.length() : -1;
+
+ // Check if the buffer needs to be filled and if we hit EOF while doing so
+ if (expectedLength > 0 && bufferPos + expectedLength >= bufferLimit) {
+ if (!fillBuffer(expectedLength, !consume, false)) return noMatch;
+ }
int start;
- int end = bufferPos;
+ int end = this.bufferPos;
int firstEscaped = -1;
+ int contentCharsRead = 0;
+ int modifiedBufferPos = -1;
+ int startOffset = 0;
+ boolean readAnything = false;
+ boolean filled = true;
readLoop: for (;;) {
while (end < bufferLimit) {
char c = buffer[end];
+ boolean isColumnSeparator = (c == columnSeparator);
+
+ // skip leading column separator
+ if (isColumnSeparator && !readAnything) {
+ startOffset = 1;
+ contentCharsRead = -1;
+ }
+
+ readAnything = true;
+
+ if (expected != null && contentCharsRead > -1) {
+ if ((contentCharsRead < expectedLength && c != expected.charAt(contentCharsRead))
+ || contentCharsRead > expectedLength) {
+ return noMatch;
+ }
+ }
- if (c == columnSeparator || c == '\n' || c == '\r') { // end of the current column
+ if (c == '\n' || c == '\r' || (isColumnSeparator && stopAtNextCol && contentCharsRead > -1)) { // stop reading
start = bufferPos;
- bufferPos = end;
+ modifiedBufferPos = end;
- // seek to the start of the next column
- if (c == columnSeparator) {
- bufferPos++;
- } else {
+ if (!isColumnSeparator && (consume || expected != null)) {
eol = true;
}
@@ -129,13 +147,14 @@ public String nextCol(boolean unescape) throws IOException {
firstEscaped = bufferPos;
}
+ contentCharsRead++;
end++;
}
// buffer ran out, refill
int oldStart = bufferPos;
- boolean filled = fillBuffer(end - bufferPos + 1);
+ filled = fillBuffer(end - bufferPos + 1, !consume, consume);
int posShift = bufferPos - oldStart; // fillBuffer may compact the data, shifting it to the buffer start
assert posShift <= 0;
end += posShift;
@@ -143,74 +162,70 @@ public String nextCol(boolean unescape) throws IOException {
if (!filled) {
start = bufferPos;
- bufferPos = end;
- eol = true;
break;
}
}
- int len = end - start;
+ start += startOffset;
+ String ret;
- if (len == 0) {
- return "";
- } else if (firstEscaped >= 0) {
- return Tiny2Util.unescape(String.valueOf(buffer, start, len));
+ if (expected != null) {
+ consume = true;
+ ret = expected;
} else {
- return String.valueOf(buffer, start, len);
+ int len = end - start;
+
+ if (len == 0) {
+ ret = readAnything ? "" : null;
+ } else if (firstEscaped >= 0) {
+ ret = Tiny2Util.unescape(String.valueOf(buffer, start, len));
+ } else {
+ ret = String.valueOf(buffer, start, len);
+ }
}
- }
-
- /**
- * Read and consume all column until eol and unescape if requested.
- */
- @Nullable
- public String nextCols(boolean unescape) throws IOException {
- if (eol) return null;
- int end = bufferPos;
- int firstEscaped = -1;
- boolean filled;
+ if (consume) {
+ if (readAnything) bof = false;
+ if (!filled) eof = eol = true;
+ if (modifiedBufferPos != -1) bufferPos = modifiedBufferPos;
- readLoop: do {
- while (end < bufferLimit) {
- char c = buffer[end];
+ if (eol && !eof) { // manually check for EOF
+ int charsToRead = buffer[bufferPos] == '\r' ? 2 : 1; // 2 for \r\n, 1 for just \n
- if (c == '\n' || c == '\r') { // end of the current column
- break readLoop;
- } else if (unescape && c == '\\' && firstEscaped < 0) {
- firstEscaped = bufferPos;
+ if (end >= bufferLimit - charsToRead) {
+ fillBuffer(charsToRead, false, true);
}
-
- end++;
}
+ }
- // buffer ran out, refill
-
- int oldStart = bufferPos;
- filled = fillBuffer(end - bufferPos + 1);
- int posShift = bufferPos - oldStart; // fillBuffer may compact the data, shifting it to the buffer start
- assert posShift <= 0;
- end += posShift;
- if (firstEscaped >= 0) firstEscaped += posShift;
- } while (filled);
-
- int start = bufferPos;
- bufferPos = end;
- eol = true;
+ return ret;
+ }
- int len = end - start;
+ /**
+ * Read and consume all columns until EOL, and unescape if requested.
+ *
+ * @return {@code null} if nothing has been read (first char was EOL), otherwise the read string (may be empty).
+ */
+ @Nullable
+ public String nextCols(boolean unescape) throws IOException {
+ return read(unescape, true, false, null);
+ }
- if (len == 0) {
- return "";
- } else if (firstEscaped >= 0) {
- return Tiny2Util.unescape(String.valueOf(buffer, start, len));
- } else {
- return String.valueOf(buffer, start, len);
- }
+ /**
+ * Read all columns until EOL without consuming, and unescape if requested.
+ * Since it doesn't consume, it won't (un)mark BOF, EOL or EOF.
+ *
+ * @return {@code null} if nothing has been read (first char was EOL), otherwise the read string (may be empty).
+ */
+ @Nullable
+ public String peekCols(boolean unescape) throws IOException {
+ return read(unescape, false, false, null);
}
/**
* Read and consume a column and convert it to integer.
+ *
+ * @return -1 if nothing has been read (first char was EOL), otherwise the number present.
*/
public int nextIntCol() throws IOException {
String str = nextCol(false);
@@ -223,87 +238,167 @@ public int nextIntCol() throws IOException {
}
public boolean nextLine(int indent) throws IOException {
- fillLopo: do {
+ fillLoop: do {
while (bufferPos < bufferLimit) {
char c = buffer[bufferPos];
if (c == '\n') {
if (indent == 0) { // skip empty lines if indent is 0
- if (!fillBuffer(2)) break fillLopo;
+ if (!fillBuffer(2, false, true)) break fillLoop;
c = buffer[bufferPos + 1];
if (c == '\n' || c == '\r') { // 2+ consecutive new lines, consume first nl and retry
bufferPos++;
lineNumber++;
+ bof = false;
continue;
}
}
- if (!fillBuffer(indent + 1)) return false;
+ if (!fillBuffer(indent + 1, false, true)) return false;
for (int i = 1; i <= indent; i++) {
- if (buffer[bufferPos + i] != '\t') return false;
+ if (buffer[bufferPos + i] != indentationChar) return false;
}
bufferPos += indent + 1;
lineNumber++;
+ bof = false;
eol = false;
return true;
}
bufferPos++;
+ bof = false;
}
- } while (fillBuffer(1));
+ } while (fillBuffer(1, false, true));
return false;
}
public boolean hasExtraIndents() throws IOException {
- return fillBuffer(1) && buffer[bufferPos] == '\t';
+ return fillBuffer(1, false, false) && buffer[bufferPos] == indentationChar;
}
public int getLineNumber() {
return lineNumber;
}
+ /**
+ * Whether or not EOL has been encountered in the current line yet.
+ */
+ public boolean isAtEol() {
+ return eol;
+ }
+
+ public boolean isAtBof() {
+ return bof;
+ }
+
public boolean isAtEof() {
return eof;
}
- public void mark() {
- if (bufferPos > 0) {
+ /**
+ * Marks the present position in the stream. Subsequent calls to
+ * {@link #reset()} will reposition the stream to this point.
+ * In comparison to {@link java.io.Reader#mark(int)} this method stacks,
+ * so don't forget to call {@link #discardMark()} if you don't need the mark anymore.
+ *
+ * @return the mark index (starting at 1)
+ */
+ public int mark() {
+ if (markIdx == 0 && bufferPos > 0) { // save memory
int available = bufferLimit - bufferPos;
System.arraycopy(buffer, bufferPos, buffer, 0, available);
bufferPos = 0;
bufferLimit = available;
- markedLineNumber = lineNumber;
- markedEol = eol;
- markedEof = eof;
}
- mark = bufferPos;
+ if (markIdx == markedBufferPositions.length) {
+ markedBufferPositions = Arrays.copyOf(markedBufferPositions, markedBufferPositions.length * 2);
+ markedLineNumbers = Arrays.copyOf(markedLineNumbers, markedLineNumbers.length * 2);
+ markedBofs = Arrays.copyOf(markedBofs, markedBofs.length * 2);
+ markedEols = Arrays.copyOf(markedEols, markedEols.length * 2);
+ markedEofs = Arrays.copyOf(markedEofs, markedEofs.length * 2);
+ }
+
+ markedBufferPositions[markIdx] = bufferPos;
+ markedLineNumbers[markIdx] = lineNumber;
+ markedBofs[markIdx] = bof;
+ markedEols[markIdx] = eol;
+ markedEofs[markIdx] = eof;
+
+ return ++markIdx;
}
- public void reset() {
- if (mark < 0) throw new IllegalStateException("not marked");
+ /**
+ * Discard the last mark.
+ */
+ public void discardMark() {
+ discardMark(markIdx);
+ }
+
+ /**
+ * Discard the mark at specified index and all above, if present.
+ */
+ private void discardMark(int index) {
+ if (markIdx == 0) throw new IllegalStateException("no mark to discard");
+ if (index < 1 || index > markIdx) throw new IllegalStateException("index out of bounds");
+
+ for (int i = markIdx; i >= index; i--) {
+ markedBufferPositions[i-1] = 0;
+ markedLineNumbers[i-1] = 0;
+ }
+
+ markIdx = index - 1;
+ }
+
+ /**
+ * Reset to last mark. The marked data isn't discarded, so can be called multiple times.
+ * If you want to reset to an older mark, use {@link #reset(int)}.
+ *
+ * @return The index of the mark that was reset to.
+ */
+ public int reset() {
+ reset(markIdx);
+ return markIdx;
+ }
+
+ /**
+ * Reset to the mark with the specified index.
+ * Unless reset to 0, the marked data isn't discarded afterwards,
+ * so can be called multiple times.
+ * Use negative indices to reset to a mark relative to the current one.
+ */
+ public void reset(int indexToResetTo) {
+ if (markIdx == 0) throw new IllegalStateException("no mark to reset to");
+ if (indexToResetTo < -markIdx || indexToResetTo > markIdx) throw new IllegalStateException("index out of bounds");
+
+ if (indexToResetTo < 0) indexToResetTo += markIdx;
+ int arrayIdx = indexToResetTo == 0 ? indexToResetTo : indexToResetTo - 1;
+
+ bufferPos = markedBufferPositions[arrayIdx];
+ lineNumber = markedLineNumbers[arrayIdx];
+ bof = markedBofs[arrayIdx];
+ eol = markedEols[arrayIdx];
+ eof = markedEofs[arrayIdx];
- bufferPos = mark;
- lineNumber = markedLineNumber;
- eol = markedEol;
- eof = markedEof;
+ if (indexToResetTo == 0) discardMark(1);
+ markIdx = indexToResetTo;
}
- private boolean fillBuffer(int count) throws IOException {
+ private boolean fillBuffer(int count, boolean preventCompaction, boolean markEof) throws IOException {
int available = bufferLimit - bufferPos;
int req = count - available;
if (req <= 0) return true;
if (bufferPos + count > buffer.length) { // not enough remaining buffer space
- if (mark >= 0) { // marked for rewind -> grow
+ if (markIdx > 0 || preventCompaction) { // can't compact -> grow
buffer = Arrays.copyOf(buffer, Math.max(bufferPos + count, buffer.length * 2));
- } else { // not marked, compact and grow as needed
+ } else { // compact and grow as needed
if (count > buffer.length) { // too small for compacting to suffice -> grow and compact
char[] newBuffer = new char[Math.max(count, buffer.length * 2)];
System.arraycopy(buffer, bufferPos, newBuffer, 0, available);
@@ -323,7 +418,7 @@ private boolean fillBuffer(int count) throws IOException {
int read = reader.read(buffer, bufferLimit, buffer.length - bufferLimit);
if (read < 0) {
- eof = eol = true;
+ if (markEof) eof = eol = true;
return false;
}
@@ -333,16 +428,21 @@ private boolean fillBuffer(int count) throws IOException {
return true;
}
+ private static final String noMatch = new String();
private final Reader reader;
+ private final char indentationChar;
private final char columnSeparator;
private char[] buffer = new char[4096 * 4];
private int bufferPos;
private int bufferLimit;
- private int mark = -1;
private int lineNumber = 1;
+ private boolean bof = true;
private boolean eol; // tracks whether the last column has been read, otherwise ambiguous if the last col is empty
private boolean eof;
- private int markedLineNumber;
- private boolean markedEol;
- private boolean markedEof;
+ private int markIdx = 0; // 0 means no mark
+ private int[] markedBufferPositions = new int[3];
+ private int[] markedLineNumbers = new int[3];
+ private boolean[] markedBofs = new boolean[3];
+ private boolean[] markedEols = new boolean[3];
+ private boolean[] markedEofs = new boolean[3];
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
index 3590ccde..c44e64a1 100644
--- a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
@@ -45,7 +45,7 @@ public static void read(Reader reader, MappingVisitor visitor) throws IOExceptio
}
public static void read(Reader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
- read(new ColumnFileReader(reader, ' '), sourceNs, targetNs, visitor);
+ read(new ColumnFileReader(reader, '\t', ' '), sourceNs, targetNs, visitor);
}
public static void read(ColumnFileReader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
diff --git a/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java b/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java
index 76b4a842..7700db8b 100644
--- a/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java
@@ -39,7 +39,7 @@ public static void read(Reader reader, MappingVisitor visitor) throws IOExceptio
}
public static void read(Reader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
- read(new ColumnFileReader(reader, ' '), sourceNs, targetNs, visitor);
+ read(new ColumnFileReader(reader, '\t', ' '), sourceNs, targetNs, visitor);
}
private static void read(ColumnFileReader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
diff --git a/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java
index 4d4c3c59..8b4c9358 100644
--- a/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java
@@ -42,7 +42,7 @@ public static void read(Reader reader, MappingVisitor visitor) throws IOExceptio
}
public static void read(Reader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
- read(new ColumnFileReader(reader, ' '), sourceNs, targetNs, visitor);
+ read(new ColumnFileReader(reader, '\t', ' '), sourceNs, targetNs, visitor);
}
private static void read(ColumnFileReader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/JamFileReader.java b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileReader.java
index 2f9b57a4..8ad1eb47 100644
--- a/src/main/java/net/fabricmc/mappingio/format/srg/JamFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileReader.java
@@ -45,7 +45,7 @@ public static void read(Reader reader, MappingVisitor visitor) throws IOExceptio
}
public static void read(Reader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
- read(new ColumnFileReader(reader, ' '), sourceNs, targetNs, visitor);
+ read(new ColumnFileReader(reader, '\t', ' '), sourceNs, targetNs, visitor);
}
private static void read(ColumnFileReader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileReader.java b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileReader.java
index 912fc6a7..77b0360c 100644
--- a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileReader.java
@@ -46,7 +46,7 @@ public static void read(Reader reader, MappingVisitor visitor) throws IOExceptio
}
public static void read(Reader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
- read(new ColumnFileReader(reader, ' '), sourceNs, targetNs, visitor);
+ read(new ColumnFileReader(reader, '\t', ' '), sourceNs, targetNs, visitor);
}
private static void read(ColumnFileReader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java
index dec67e7a..c50ba709 100644
--- a/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java
@@ -16,7 +16,6 @@
package net.fabricmc.mappingio.format.srg;
-import java.io.CharArrayReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
@@ -44,7 +43,7 @@ private TsrgFileReader() {
}
public static List