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
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,41 @@ private static boolean isQuotedString(String value) {
}

private static String processEscapeSequences(String value) {
return value
.replace("\\\"", "\"")
.replace("\\\\", "\\")
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t");
StringBuilder result = new StringBuilder(value.length());
int i = 0;
while (i < value.length()) {
i = appendChar(result, value, i);
}
return result.toString();
}

private static int appendChar(StringBuilder result, String value, int i) {
char c = value.charAt(i);
if (c != '\\' || i + 1 >= value.length()) {
result.append(c);
return i + 1;
}
return appendEscaped(result, value.charAt(i + 1), i);
}

private static int appendEscaped(StringBuilder result, char escaped, int i) {
String replacement = replacementFor(escaped);
if (replacement == null) {
result.append('\\');
return i + 1;
}
result.append(replacement);
return i + 2;
}

private static String replacementFor(char escaped) {
return switch (escaped) {
case '"' -> "\"";
case '\\' -> "\\";
case 'n' -> "\n";
case 'r' -> "\r";
case 't' -> "\t";
default -> null;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,18 @@ public static int grownSize(int currentLength) {
return Math.max((int) (currentLength * MULTIPLIER), currentLength + 1);
}

/** The slot index probed on the given {@code iteration} of the sequence. */
/**
* The slot index probed on the given {@code iteration} of the sequence.
*
* <p>The step {@code h2} is derived from {@code Math.abs(hashCode % (length - 1))}
* rather than {@code Math.abs(hashCode) % (length - 1)}: the two agree for every
* {@code hashCode} except {@link Integer#MIN_VALUE}, whose {@code Math.abs} stays
* negative and would otherwise yield a non-positive step that folds the probe
* sequence back onto a handful of slots.
*/
public static int probe(int hashCode, int prime, int length, int iteration) {
int h1 = prime - (hashCode % prime);
int h2 = 1 + (Math.abs(hashCode) % (length - 1));
int h2 = 1 + Math.abs(hashCode % (length - 1));
return Math.abs((h1 + (iteration * h2)) % length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ public void unquoteResolvesEscapeSequences() {
assertEquals("line\nbreak", ToonSyntax.unquote("\"line\\nbreak\""));
}

@Test
public void unquoteKeepsEscapedBackslashLiteralBeforeControlLetter() {
// The raw value is a \ \ n b : an escaped backslash followed by a literal
// 'n', so it must decode to a backslash and an 'n', not to a newline.
assertEquals("a\\nb", ToonSyntax.unquote("\"a\\\\nb\""));
assertEquals("a\\tb", ToonSyntax.unquote("\"a\\\\tb\""));
}

@Test
public void unquoteLeavesUnknownEscapeUntouched() {
assertEquals("a\\xb", ToonSyntax.unquote("\"a\\xb\""));
}

@Test
public void keyValuePatternMatchesDottedKeys() {
Matcher matcher = ToonSyntax.KEY_VALUE_PATTERN.matcher("user.name: Alice");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.HashSet;
import java.util.Set;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;

Expand Down Expand Up @@ -79,4 +82,24 @@ private void assertProbesAreValidIndices(int hashCode, int prime, int length) {
"index " + index + " out of range for hashCode " + hashCode);
}
}

@Test
public void probeVisitsEverySlotForMinValueHashCode() {
// Integer.MIN_VALUE is the one hashCode whose Math.abs stays negative. A prior
// step derived from Math.abs(hashCode) went non-positive for it, folding the
// sequence back onto a subset of the slots (12 of 13 here). On a prime-sized
// table the sequence must be a full permutation of every slot.
int length = 13;
int prime = 11;
assertProbesAllDistinct(Integer.MIN_VALUE, prime, length);
}

private void assertProbesAllDistinct(int hashCode, int prime, int length) {
Set<Integer> visited = new HashSet<>();
for (int iteration = 0; iteration < length; iteration++) {
visited.add(DoubleHashing.probe(hashCode, prime, length, iteration));
}
assertEquals(length, visited.size(),
"probe should visit every slot exactly once for hashCode " + hashCode);
}
}
Loading