3
0
Mirror von https://github.com/ViaVersion/ViaVersion.git synchronisiert 2024-12-28 17:10:13 +01:00

Update adventure nbt i/o

Dieser Commit ist enthalten in:
Nassim Jahnke 2021-09-25 13:15:26 +02:00
Ursprung 12e2be40e8
Commit 88165088a9
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 6BE3B555EBC5982B
6 geänderte Dateien mit 168 neuen und 92 gelöschten Zeilen

Datei anzeigen

@ -39,8 +39,12 @@ import java.nio.file.Path;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
// Specific Via changes:
// - Use OpenNBT tags
// - Added readString/writeString methods from TagStringIO
// - Has not been updated for the sake of keeping the class simple
/** /**
* See https://github.com/KyoriPowered/adventure. * Serialization operations for binary tags.
*/ */
public final class BinaryTagIO { public final class BinaryTagIO {
private BinaryTagIO() { private BinaryTagIO() {

Datei anzeigen

@ -1,7 +1,7 @@
/* /*
* This file is part of adventure, licensed under the MIT License. * This file is part of adventure, licensed under the MIT License.
* *
* Copyright (c) 2017-2020 KyoriPowered * Copyright (c) 2017-2021 KyoriPowered
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -23,7 +23,10 @@
*/ */
package com.viaversion.viaversion.api.minecraft.nbt; package com.viaversion.viaversion.api.minecraft.nbt;
/* package */ final class CharBuffer { /**
* A character buffer designed to be inspected by a parser.
*/
final class CharBuffer {
private final CharSequence sequence; private final CharSequence sequence;
private int index; private int index;
@ -32,7 +35,7 @@ package com.viaversion.viaversion.api.minecraft.nbt;
} }
/** /**
* Get the character at the current position * Get the character at the current position.
* *
* @return The current character * @return The current character
*/ */
@ -45,7 +48,7 @@ package com.viaversion.viaversion.api.minecraft.nbt;
} }
/** /**
* Get the current character and advance * Get the current character and advance.
* *
* @return current character * @return current character
*/ */
@ -62,6 +65,10 @@ package com.viaversion.viaversion.api.minecraft.nbt;
return this.index < this.sequence.length(); return this.index < this.sequence.length();
} }
public boolean hasMore(final int offset) {
return this.index + offset < this.sequence.length();
}
/** /**
* Search for the provided token, and advance the reader index past the {@code until} character. * Search for the provided token, and advance the reader index past the {@code until} character.
* *
@ -109,6 +116,23 @@ package com.viaversion.viaversion.api.minecraft.nbt;
return this; return this;
} }
/**
* If the next non-whitespace character is {@code token}, advance past it.
*
* <p>This method always consumes whitespace.</p>
*
* @param token next non-whitespace character to query
* @return if the next non-whitespace character is {@code token}
*/
public boolean takeIf(final char token) {
this.skipWhitespace();
if (this.hasMore() && this.peek() == token) {
this.advance();
return true;
}
return false;
}
public CharBuffer skipWhitespace() { public CharBuffer skipWhitespace() {
while (this.hasMore() && Character.isWhitespace(this.peek())) this.advance(); while (this.hasMore() && Character.isWhitespace(this.peek())) this.advance();
return this; return this;

Datei anzeigen

@ -26,9 +26,9 @@ package com.viaversion.viaversion.api.minecraft.nbt;
import java.io.IOException; import java.io.IOException;
/** /**
* An exception thrown when parsing a string tag * An exception thrown when parsing a string tag.
*/ */
/* package */ class StringTagParseException extends IOException { class StringTagParseException extends IOException {
private static final long serialVersionUID = -3001637554903912905L; private static final long serialVersionUID = -3001637554903912905L;
private final CharSequence buffer; private final CharSequence buffer;
private final int position; private final int position;

Datei anzeigen

@ -1,7 +1,7 @@
/* /*
* This file is part of adventure, licensed under the MIT License. * This file is part of adventure, licensed under the MIT License.
* *
* Copyright (c) 2017-2020 KyoriPowered * Copyright (c) 2017-2021 KyoriPowered
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -37,33 +37,39 @@ import com.github.steveice10.opennbt.tag.builtin.NumberTag;
import com.github.steveice10.opennbt.tag.builtin.ShortTag; import com.github.steveice10.opennbt.tag.builtin.ShortTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag; import com.github.steveice10.opennbt.tag.builtin.Tag;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.stream.LongStream;
// Specific Via changes:
// - Use OpenNBT tags
// - Small byteArray() optimization
// - acceptLegacy = true by default
final class TagStringReader {
private static final int MAX_DEPTH = 512;
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private static final int[] EMPTY_INT_ARRAY = new int[0];
private static final long[] EMPTY_LONG_ARRAY = new long[0];
/**
* See https://github.com/KyoriPowered/adventure.
*/
/* package */ final class TagStringReader {
private final CharBuffer buffer; private final CharBuffer buffer;
private boolean acceptLegacy = true;
private int depth;
public TagStringReader(final CharBuffer buffer) { TagStringReader(final CharBuffer buffer) {
this.buffer = buffer; this.buffer = buffer;
} }
public CompoundTag compound() throws StringTagParseException { public CompoundTag compound() throws StringTagParseException {
this.buffer.expect(Tokens.COMPOUND_BEGIN); this.buffer.expect(Tokens.COMPOUND_BEGIN);
final CompoundTag compoundTag = new CompoundTag(); final CompoundTag compoundTag = new CompoundTag();
if (this.buffer.peek() == Tokens.COMPOUND_END) { if (this.buffer.takeIf(Tokens.COMPOUND_END)) {
this.buffer.take();
return compoundTag; return compoundTag;
} }
while (this.buffer.hasMore()) { while (this.buffer.hasMore()) {
final String key = this.key(); compoundTag.put(this.key(), this.tag());
final Tag tag = this.tag();
compoundTag.put(key, tag);
if (this.separatorOrCompleteWith(Tokens.COMPOUND_END)) { if (this.separatorOrCompleteWith(Tokens.COMPOUND_END)) {
return compoundTag; return compoundTag;
} }
@ -74,13 +80,11 @@ import java.util.stream.IntStream;
public ListTag list() throws StringTagParseException { public ListTag list() throws StringTagParseException {
final ListTag listTag = new ListTag(); final ListTag listTag = new ListTag();
this.buffer.expect(Tokens.ARRAY_BEGIN); this.buffer.expect(Tokens.ARRAY_BEGIN);
final boolean prefixedIndex = this.buffer.peek() == '0' && this.buffer.peek(1) == ':'; final boolean prefixedIndex = this.acceptLegacy && this.buffer.peek() == '0' && this.buffer.peek(1) == ':';
while (this.buffer.hasMore()) { if (!prefixedIndex && this.buffer.takeIf(Tokens.ARRAY_END)) {
if (this.buffer.peek() == Tokens.ARRAY_END) {
this.buffer.advance();
return listTag; return listTag;
} }
while (this.buffer.hasMore()) {
if (prefixedIndex) { if (prefixedIndex) {
this.buffer.takeUntil(':'); this.buffer.takeUntil(':');
} }
@ -99,11 +103,12 @@ import java.util.stream.IntStream;
* *
* @return array-typed tag * @return array-typed tag
*/ */
public Tag array(final char elementType) throws StringTagParseException { public Tag array(char elementType) throws StringTagParseException {
this.buffer.expect(Tokens.ARRAY_BEGIN) this.buffer.expect(Tokens.ARRAY_BEGIN)
.expect(elementType) .expect(elementType)
.expect(Tokens.ARRAY_SIGNATURE_SEPARATOR); .expect(Tokens.ARRAY_SIGNATURE_SEPARATOR);
elementType = Character.toLowerCase(elementType);
if (elementType == Tokens.TYPE_BYTE) { if (elementType == Tokens.TYPE_BYTE) {
return new ByteArrayTag(this.byteArray()); return new ByteArrayTag(this.byteArray());
} else if (elementType == Tokens.TYPE_INT) { } else if (elementType == Tokens.TYPE_INT) {
@ -116,11 +121,15 @@ import java.util.stream.IntStream;
} }
private byte[] byteArray() throws StringTagParseException { private byte[] byteArray() throws StringTagParseException {
final List<Byte> bytes = new ArrayList<>(); if (this.buffer.takeIf(Tokens.ARRAY_END)) {
return EMPTY_BYTE_ARRAY;
}
final IntList bytes = new IntArrayList();
while (this.buffer.hasMore()) { while (this.buffer.hasMore()) {
final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_BYTE); final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_BYTE);
try { try {
bytes.add(Byte.valueOf(value.toString())); bytes.add(Byte.parseByte(value.toString()));
} catch (final NumberFormatException ex) { } catch (final NumberFormatException ex) {
throw this.buffer.makeError("All elements of a byte array must be bytes!"); throw this.buffer.makeError("All elements of a byte array must be bytes!");
} }
@ -128,7 +137,7 @@ import java.util.stream.IntStream;
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) { if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
final byte[] result = new byte[bytes.size()]; final byte[] result = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); ++i) { for (int i = 0; i < bytes.size(); ++i) {
result[i] = bytes.get(i); result[i] = (byte) bytes.getInt(i);
} }
return result; return result;
} }
@ -137,6 +146,10 @@ import java.util.stream.IntStream;
} }
private int[] intArray() throws StringTagParseException { private int[] intArray() throws StringTagParseException {
if (this.buffer.takeIf(Tokens.ARRAY_END)) {
return EMPTY_INT_ARRAY;
}
final IntStream.Builder builder = IntStream.builder(); final IntStream.Builder builder = IntStream.builder();
while (this.buffer.hasMore()) { while (this.buffer.hasMore()) {
final Tag value = this.tag(); final Tag value = this.tag();
@ -152,21 +165,21 @@ import java.util.stream.IntStream;
} }
private long[] longArray() throws StringTagParseException { private long[] longArray() throws StringTagParseException {
final List<Long> longs = new ArrayList<>(); if (this.buffer.takeIf(Tokens.ARRAY_END)) {
return EMPTY_LONG_ARRAY;
}
final LongStream.Builder longs = LongStream.builder();
while (this.buffer.hasMore()) { while (this.buffer.hasMore()) {
final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_LONG); final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_LONG);
try { try {
longs.add(Long.valueOf(value.toString())); longs.add(Long.parseLong(value.toString()));
} catch (final NumberFormatException ex) { } catch (final NumberFormatException ex) {
throw this.buffer.makeError("All elements of a long array must be longs!"); throw this.buffer.makeError("All elements of a long array must be longs!");
} }
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) { if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
final long[] result = new long[longs.size()]; return longs.build().toArray();
for (int i = 0; i < longs.size(); ++i) {
result[i] = longs.get(i);
}
return result;
} }
} }
throw this.buffer.makeError("Reached end of document without array close"); throw this.buffer.makeError("Reached end of document without array close");
@ -181,7 +194,21 @@ import java.util.stream.IntStream;
} }
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
while (this.buffer.peek() != ':') { // DO NOT CHECK FOR CHARACTER VALIDITY; LEGACY NBT ALLOWS ANY CHARACTER, EVEN WHEN UNQUOTED while (this.buffer.hasMore()) {
final char peek = this.buffer.peek();
if (!Tokens.id(peek)) {
if (this.acceptLegacy) {
// In legacy format, a key is any non-colon character, with escapes allowed
if (peek == Tokens.ESCAPE_MARKER) {
this.buffer.take(); // skip
continue;
} else if (peek != Tokens.COMPOUND_KEY_TERMINATOR) {
builder.append(this.buffer.take());
continue;
}
}
break;
}
builder.append(this.buffer.take()); builder.append(this.buffer.take());
} }
return builder.toString(); return builder.toString();
@ -191,12 +218,17 @@ import java.util.stream.IntStream;
} }
public Tag tag() throws StringTagParseException { public Tag tag() throws StringTagParseException {
if (this.depth++ > MAX_DEPTH) {
throw this.buffer.makeError("Exceeded maximum allowed depth of " + MAX_DEPTH + " when reading tag");
}
try {
final char startToken = this.buffer.skipWhitespace().peek(); final char startToken = this.buffer.skipWhitespace().peek();
switch (startToken) { switch (startToken) {
case Tokens.COMPOUND_BEGIN: case Tokens.COMPOUND_BEGIN:
return this.compound(); return this.compound();
case Tokens.ARRAY_BEGIN: case Tokens.ARRAY_BEGIN:
if (this.buffer.peek(2) == ';') { // we know we're an array tag // Maybe add in a legacy-only mode to read those?
if (this.buffer.hasMore(2) && this.buffer.peek(2) == ';') { // we know we're an array tag
return this.array(this.buffer.peek(1)); return this.array(this.buffer.peek(1));
} else { } else {
return this.list(); return this.list();
@ -209,12 +241,15 @@ import java.util.stream.IntStream;
default: // scalar default: // scalar
return this.scalar(); return this.scalar();
} }
} finally {
this.depth--;
}
} }
/** /**
* A tag that is definitely some sort of scalar * A tag that is definitely some sort of scalar.
* *
* <p>Does not detect quoted strings, so </p> * <p>Does not detect quoted strings, so those should have been parsed already.</p>
* *
* @return a parsed tag * @return a parsed tag
*/ */
@ -227,7 +262,7 @@ import java.util.stream.IntStream;
if (builder.length() != 0) { if (builder.length() != 0) {
Tag result = null; Tag result = null;
try { try {
switch (Character.toUpperCase(current)) { // try to read and return as a number switch (Character.toLowerCase(current)) { // try to read and return as a number
// case Tokens.TYPE_INTEGER: // handled below, ints are ~special~ // case Tokens.TYPE_INTEGER: // handled below, ints are ~special~
case Tokens.TYPE_BYTE: case Tokens.TYPE_BYTE:
result = new ByteTag(Byte.parseByte(builder.toString())); result = new ByteTag(Byte.parseByte(builder.toString()));
@ -269,29 +304,33 @@ import java.util.stream.IntStream;
try { try {
return new IntTag(Integer.parseInt(built)); return new IntTag(Integer.parseInt(built));
} catch (final NumberFormatException ex) { } catch (final NumberFormatException ex) {
try {
return new DoubleTag(Double.parseDouble(built));
} catch (final NumberFormatException ex2) {
// ignore // ignore
} }
} }
}
if (built.equalsIgnoreCase(Tokens.LITERAL_TRUE)) {
return new ByteTag((byte) 1);
} else if (built.equalsIgnoreCase(Tokens.LITERAL_FALSE)) {
return new ByteTag((byte) 0);
}
return new StringTag(built); return new StringTag(built);
} }
private boolean separatorOrCompleteWith(final char endCharacter) throws StringTagParseException { private boolean separatorOrCompleteWith(final char endCharacter) throws StringTagParseException {
if (this.buffer.skipWhitespace().peek() == endCharacter) { if (this.buffer.takeIf(endCharacter)) {
this.buffer.take();
return true; return true;
} }
this.buffer.expect(Tokens.VALUE_SEPARATOR); this.buffer.expect(Tokens.VALUE_SEPARATOR);
if (this.buffer.skipWhitespace().peek() == endCharacter) {
this.buffer.take();
return true;
}
return false; return false;
} }
/** /**
* Remove simple escape sequences from a string * Remove simple escape sequences from a string.
* *
* @param withEscapes input string with escapes * @param withEscapes input string with escapes
* @return string with escapes processed * @return string with escapes processed
@ -310,4 +349,8 @@ import java.util.stream.IntStream;
output.append(withEscapes.substring(lastEscape)); output.append(withEscapes.substring(lastEscape));
return output.toString(); return output.toString();
} }
public void legacy(final boolean acceptLegacy) {
this.acceptLegacy = acceptLegacy;
}
} }

Datei anzeigen

@ -33,6 +33,7 @@ import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag; import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag; import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.github.steveice10.opennbt.tag.builtin.NumberTag;
import com.github.steveice10.opennbt.tag.builtin.ShortTag; import com.github.steveice10.opennbt.tag.builtin.ShortTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag; import com.github.steveice10.opennbt.tag.builtin.Tag;
@ -41,12 +42,16 @@ import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.Map; import java.util.Map;
// Specific Via changes:
// - Use OpenNBT tags
// - Has not been updated to support pretty printing and legacy writing since that is not needed
/** /**
* See https://github.com/KyoriPowered/adventure. * An emitter for the SNBT format.
*
* <p>Details on the format are described in the package documentation.</p>
*/ */
/* package */ final class TagStringWriter implements AutoCloseable { final class TagStringWriter implements AutoCloseable {
private final Appendable out; private final Appendable out;
private final String indent = " ";
private int level; private int level;
/** /**
* Whether a {@link Tokens#VALUE_SEPARATOR} needs to be printed before the beginning of the next object. * Whether a {@link Tokens#VALUE_SEPARATOR} needs to be printed before the beginning of the next object.
@ -73,17 +78,17 @@ import java.util.Map;
} else if (tag instanceof StringTag) { } else if (tag instanceof StringTag) {
return this.value(((StringTag) tag).getValue(), Tokens.EOF); return this.value(((StringTag) tag).getValue(), Tokens.EOF);
} else if (tag instanceof ByteTag) { } else if (tag instanceof ByteTag) {
return this.value(Byte.toString(((ByteTag) tag).asByte()), Tokens.TYPE_BYTE); return this.value(Byte.toString(((NumberTag) tag).asByte()), Tokens.TYPE_BYTE);
} else if (tag instanceof ShortTag) { } else if (tag instanceof ShortTag) {
return this.value(Short.toString(((ShortTag) tag).asShort()), Tokens.TYPE_SHORT); return this.value(Short.toString(((NumberTag) tag).asShort()), Tokens.TYPE_SHORT);
} else if (tag instanceof IntTag) { } else if (tag instanceof IntTag) {
return this.value(Integer.toString(((IntTag) tag).asInt()), Tokens.TYPE_INT); return this.value(Integer.toString(((NumberTag) tag).asInt()), Tokens.TYPE_INT);
} else if (tag instanceof LongTag) { } else if (tag instanceof LongTag) {
return this.value(Long.toString(((LongTag) tag).asLong()), Tokens.TYPE_LONG); return this.value(Long.toString(((NumberTag) tag).asLong()), Character.toUpperCase(Tokens.TYPE_LONG)); // special case
} else if (tag instanceof FloatTag) { } else if (tag instanceof FloatTag) {
return this.value(Float.toString(((FloatTag) tag).asFloat()), Tokens.TYPE_FLOAT); return this.value(Float.toString(((NumberTag) tag).asFloat()), Tokens.TYPE_FLOAT);
} else if (tag instanceof DoubleTag) { } else if (tag instanceof DoubleTag) {
return this.value(Double.toString(((DoubleTag) tag).asDouble()), Tokens.TYPE_DOUBLE); return this.value(Double.toString(((NumberTag) tag).asDouble()), Tokens.TYPE_DOUBLE);
} else { } else {
throw new IOException("Unknown tag type: " + tag.getClass().getSimpleName()); throw new IOException("Unknown tag type: " + tag.getClass().getSimpleName());
// unknown! // unknown!
@ -92,7 +97,7 @@ import java.util.Map;
private TagStringWriter writeCompound(final CompoundTag tag) throws IOException { private TagStringWriter writeCompound(final CompoundTag tag) throws IOException {
this.beginCompound(); this.beginCompound();
for (Map.Entry<String, Tag> entry : tag.entrySet()) { for (final Map.Entry<String, Tag> entry : tag.entrySet()) {
this.key(entry.getKey()); this.key(entry.getKey());
this.writeTag(entry.getValue()); this.writeTag(entry.getValue());
} }
@ -244,7 +249,6 @@ import java.util.Map;
} }
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (this.level != 0) { if (this.level != 0) {

Datei anzeigen

@ -1,7 +1,7 @@
/* /*
* This file is part of adventure, licensed under the MIT License. * This file is part of adventure, licensed under the MIT License.
* *
* Copyright (c) 2017-2020 KyoriPowered * Copyright (c) 2017-2021 KyoriPowered
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -23,10 +23,7 @@
*/ */
package com.viaversion.viaversion.api.minecraft.nbt; package com.viaversion.viaversion.api.minecraft.nbt;
/** final class Tokens {
* See https://github.com/KyoriPowered/adventure.
*/
/* package */ final class Tokens {
// Compounds // Compounds
static final char COMPOUND_BEGIN = '{'; static final char COMPOUND_BEGIN = '{';
static final char COMPOUND_END = '}'; static final char COMPOUND_END = '}';
@ -43,20 +40,24 @@ package com.viaversion.viaversion.api.minecraft.nbt;
static final char DOUBLE_QUOTE = '"'; static final char DOUBLE_QUOTE = '"';
static final char ESCAPE_MARKER = '\\'; static final char ESCAPE_MARKER = '\\';
static final char TYPE_BYTE = 'B'; static final char TYPE_BYTE = 'b';
static final char TYPE_SHORT = 'S'; static final char TYPE_SHORT = 's';
static final char TYPE_INT = 'I'; // array only static final char TYPE_INT = 'i'; // array only
static final char TYPE_LONG = 'L'; static final char TYPE_LONG = 'l';
static final char TYPE_FLOAT = 'F'; static final char TYPE_FLOAT = 'f';
static final char TYPE_DOUBLE = 'D'; static final char TYPE_DOUBLE = 'd';
static final String LITERAL_TRUE = "true";
static final String LITERAL_FALSE = "false";
static final String NEWLINE = System.getProperty("line.separator", "\n");
static final char EOF = '\0'; static final char EOF = '\0';
private Tokens() { private Tokens() {
} }
/** /**
* Return if a character is a valid component in an identifier * Return if a character is a valid component in an identifier.
* *
* <p>An identifier character must match the expression {@code [a-zA-Z0-9_+.-]}</p> * <p>An identifier character must match the expression {@code [a-zA-Z0-9_+.-]}</p>
* *
@ -73,8 +74,8 @@ package com.viaversion.viaversion.api.minecraft.nbt;
/** /**
* Return whether a character could be at some position in a number. * Return whether a character could be at some position in a number.
* <p> *
* A string passing this check does not necessarily mean it is syntactically valid * <p>A string passing this check does not necessarily mean it is syntactically valid.</p>
* *
* @param c character to check * @param c character to check
* @return if possibly part of a number * @return if possibly part of a number