All Downloads are FREE. Search and download functionalities are using the official Maven repository.

personthecat.catlib.linting.SyntaxLinter Maven / Gradle / Ivy

Go to download

Utilities for serialization, commands, noise generation, IO, and some new data types.

The newest version!
package personthecat.catlib.linting;

import net.minecraft.class_124;
import net.minecraft.class_2561;
import net.minecraft.class_2568;
import net.minecraft.class_2583;
import net.minecraft.class_2585;
import net.minecraft.class_2588;
import net.minecraft.network.chat.*;

import javax.annotation.Nullable;
import javax.annotation.RegEx;
import javax.annotation.concurrent.ThreadSafe;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * An extensible, modular linter for displaying formatted data in the chat.
 *
 * 

To use this class, create a new instance by passing in an array of * {@link Highlighter highlighters}. A highlighter is an object which locates * and stylizes text. For example, the {@link RegexHighlighter} is capable * of matching entire patterns or groups in a regular expression and applying * a constant style to the individual matches. * *

{@code
 *   static final SyntaxLinter TRUE_LINTER =
 *     new SyntaxLinter({
 *        new RegexHighlighter("true", color(ChatFormatting.GOLD))
 *     });
 * }
* *

This object can then produce a formatted {@link class_2561} when provided * a body of text. * *

Note that this object is valid and safe in a multithreaded context. */ @ThreadSafe public class SyntaxLinter { public static final Pattern MULTILINE_DOC = Pattern.compile("/\\*\\*[\\s\\S]*?\\*/", Pattern.DOTALL); public static final Pattern MULTILINE_COMMENT = Pattern.compile("/\\*[\\s\\S]*?\\*/", Pattern.DOTALL); public static final Pattern LINE_TODO = Pattern.compile("(?:#|//).*(?:todo|to-do).*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); public static final Pattern LINE_DOC = Pattern.compile("(?:#!|///).*$", Pattern.MULTILINE); public static final Pattern LINE_COMMENT = Pattern.compile("(?:#|//).*$", Pattern.MULTILINE); public static final Pattern KEY = Pattern.compile("(\"[\\w\\s]*\"|\\w+)\\s*(?=:)|[-_\\w./]+\\s*(?:::|[aA][sS])\\s*\\w+(.*\\s[aA][sS]\\s+\\w+)?", Pattern.MULTILINE); public static final Pattern BOOLEAN_VALUE = Pattern.compile("(true|false)(?=\\s*,?\\s*(?:$|#|//|/\\*))", Pattern.MULTILINE); public static final Pattern NUMERIC_VALUE = Pattern.compile("(\\d+(\\.\\d+)?)(?=\\s*,?\\s*(?:$|#|//|/\\*))", Pattern.MULTILINE); public static final Pattern NULL_VALUE = Pattern.compile("(null)(?=\\s*,?\\s*(?:$|#|//|/\\*))", Pattern.MULTILINE); public static final Pattern BAD_CLOSER = Pattern.compile("[a-zA-Z]\\w*(? 0) { // Append unformatted text; formatted.method_10852(stc(text.substring(i, start))); } formatted.method_10852(h.replacement()); ctx.skipTo(end); i = end; } return formatted.method_10852(stc(text.substring(i))); } /** * Shorthand for constructing an error style when provided a raw tooltip. * * @param s The string to display as a tooltip. * @return A new {@link class_2583} used for displaying error messages. */ public static class_2583 error(final String s) { return class_2583.field_24360.method_27705(class_124.field_1061, class_124.field_1073) .method_10949(new class_2568(class_2568.class_5247.field_24342, translate(s))); } /** * Shorthand for a regular, formattable {@link class_2585}. * * @param s The text being wrapped. * @return A wrapped, formattable output containing the original message. */ public static class_2585 stc(final String s) { return new class_2585(s); } /** * Shorthand for a {@link class_2588}. * * @param s The text being wrapped. * @return A wrapped, formattable output containing the translated message. */ public static class_2588 translate(final String s) { return new class_2588(s); } /** * Returns a new {@link class_2583} with the given color. * * @param color The {@link class_124} color. * @return A new style with the given color. */ public static class_2583 color(final class_124 color) { return class_2583.field_24360.method_10977(color); } /** * This interface represents any object storing instructions for how to * highlight a body of text. It does not contain the text, nor should it * contain any mutable data for tracking the text. Rather, it should * provide an {@link Instance} which does once the text becomes available. */ public interface Highlighter { Instance get(String text); /** * This interface represents an object which tracks and applies * formatting changes to a body of text. It houses the logic * responsible for locating the text and will provide the updated * text when it becomes available. */ interface Instance { void next(); boolean found(); int start(); int end(); class_2561 replacement(); } } /** * A highlighter which applies a single pattern based on a regular expression. * *

Note that, by default, any part of the expression matched will be consumed * by this highlighter. To explicitly match the individual groups within a pattern, * apply the useGroups flag to the constructor. */ public static class RegexHighlighter implements Highlighter { final Pattern pattern; final class_2583 style; final boolean useGroups; public RegexHighlighter(final @RegEx String pattern, final class_2583 style) { this(Pattern.compile(pattern, Pattern.MULTILINE), style); } public RegexHighlighter(final Pattern pattern, final class_2583 style) { this(pattern, style, false); } public RegexHighlighter(final Pattern pattern, final class_2583 style, final boolean useGroups) { this.pattern = pattern; this.style = style; this.useGroups = useGroups; } @Override public Instance get(final String text) { return new RegexHighlighterInstance(text); } public class RegexHighlighterInstance implements Instance { final Matcher matcher; final String text; final int groups; boolean found; int group; int m; public RegexHighlighterInstance(final String text) { this.matcher = pattern.matcher(text); this.text = text; this.groups = useGroups ? matcher.groupCount() : 0; this.found = matcher.find(); this.group = found && groups > 0 ? 1 : 0; this.m = 0; } @Override public void next() { if (this.group < this.groups) { this.group++; } else { this.found = this.matcher.find(); if (this.found) { this.m++; } } } @Override public boolean found() { return this.found; } @Override public int start() { return this.matcher.start(this.group); } @Override public int end() { return this.matcher.end(this.group); } @Override public class_2561 replacement() { return new class_2585(this.text.substring(this.start(), this.end())).method_10862(this.getStyle()); } private class_2583 getStyle() { return style != null ? style : RANDOM_COLORS[this.m % RANDOM_COLORS.length]; } } } /** * A simple highlighter used for locating any unclosed or unexpected container * elements. These characters will be highlighted in red and underlined with a * tooltip displaying details about the issue. * *

Note that this highlighter is not designed for high accuracy. It is not * aware that container elements will be consumed by raw, unquoted strings. * Instead, it assumes that these errors are unrelated and expects that they * will be highlighted by some other object. This is an intentionally pragmatic * approach, but may need more testing. * *

Please share any feedback regarding this highlighter to PersonTheCat on * the GitHub repository for this project. */ public static class UnbalancedTokenHighlighter implements Highlighter { public static final UnbalancedTokenHighlighter INSTANCE = new UnbalancedTokenHighlighter(); private UnbalancedTokenHighlighter() {} @Override public Instance get(final String text) { return new UnbalancedTokenHighlighterInstance(text); } public static class UnbalancedTokenHighlighterInstance implements Instance { final BitSet unclosed = new BitSet(); final BitSet unexpected = new BitSet(); int unclosedIndex = 0; int unexpectedIndex = 0; final String text; public UnbalancedTokenHighlighterInstance(final String text) { this.text = text; this.resolveErrors(); this.next(); } private void resolveErrors() { boolean esc = false; int braces = 0; int brackets = 0; int i = 0; while (i < this.text.length()) { if (esc) { esc = false; i++; continue; } switch (this.text.charAt(i)) { case '\\' -> esc = true; case '{' -> { if (this.findClosing(i, braces, '{', '}') < 0) { this.unclosed.set(i); } braces++; } case '[' -> { if (this.findClosing(i, brackets, '[', ']') < 0) { this.unclosed.set(i); } brackets++; } case '}' -> { if (braces <= 0) { this.unexpected.set(i); } braces--; } case ']' -> { if (brackets <= 0) { this.unexpected.set(i); } brackets--; } } i++; } } private int findClosing(int opening, int ongoing, char open, char close) { int numP = ongoing; boolean dq = false; boolean sq = false; boolean esc = false; for (int i = opening; i < this.text.length(); i++) { if (esc) { esc = false; continue; } final char c = this.text.charAt(i); if (c == '\\') { esc = true; } else if (c == '"') { dq = !dq; } else if (c == '\'') { sq = !sq; } else if (c == open) { if (!dq && !sq) numP++; } else if (c == close) { if (!dq && !sq && --numP == 0) return i; } } return -1; } @Override public void next() { if (this.unclosedIndex >= 0) { this.unclosedIndex = this.unclosed.nextSetBit(this.unclosedIndex + 1); } if (this.unexpectedIndex >= 0) { this.unexpectedIndex = this.unexpected.nextSetBit(this.unexpectedIndex + 1); } } @Override public boolean found() { return this.unclosedIndex >= 0 || this.unexpectedIndex >= 0; } @Override public int start() { if (this.unexpectedIndex < 0) { return this.unclosedIndex; } else if (this.unclosedIndex < 0) { return this.unexpectedIndex; } return Math.min(this.unclosedIndex, this.unexpectedIndex); } @Override public int end() { return this.start() + 1; } @Override public class_2561 replacement() { final int start = this.start(); final char c = this.text.charAt(start); final class_2583 style = start == this.unclosedIndex ? UNCLOSED_ERROR : UNEXPECTED_ERROR; return stc(String.valueOf(c)).method_27696(style); } } } /** * The context used for highlighting text output. Essentially just a list of * {@link Matcher} -> {@link class_2583} for the given text. */ private static class Context { final List highlighters = new ArrayList<>(); final String text; Context(final String text, final Highlighter[] highlighters) { this.text = text; for (final Highlighter highlighter : highlighters) { this.highlighters.add(highlighter.get(text)); } } @Nullable Highlighter.Instance next(final int i) { // Figure out whether any other matches have been found; int start = Integer.MAX_VALUE; Highlighter.Instance first = null; for (final Highlighter.Instance h : this.highlighters) { if (!h.found()) continue; final int mStart = h.start(); if (mStart >= i && mStart < start) { start = mStart; first = h; } } return first; } void skipTo(final int i) { for (final Highlighter.Instance h : this.highlighters) { if (!h.found()) continue; if (h.end() <= i) { h.next(); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy