Skip to content

Commit

Permalink
introduced the PreProcessor
Browse files Browse the repository at this point in the history
  • Loading branch information
BlvckBytes committed Oct 18, 2024
1 parent f0969eb commit d67d55e
Show file tree
Hide file tree
Showing 22 changed files with 1,214 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.blvckbytes.bbconfigmapper.preprocessor;

import java.io.IOException;
import java.io.Writer;

public record BlankLine(String content) implements PreProcessorLine {

@Override
public void append(Writer writer) throws IOException {
writer.write(content);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package me.blvckbytes.bbconfigmapper.preprocessor;

import java.io.IOException;
import java.io.Writer;

public class CommentLine implements PreProcessorLine {

private final String indent;
public final String value;

public CommentLine(String indent, String value) {
this.indent = indent;
this.value = value;
}

@Override
public void append(Writer writer) throws IOException {
writer.write(indent);
writer.write('#');
writer.write(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package me.blvckbytes.bbconfigmapper.preprocessor;

import java.util.Objects;

public class EnvironmentComment {

public final String variableName;
private final String variableNameLower;

public final String variableType;
private final String variableTypeLower;

public final CommentLine commentLine;

public EnvironmentComment(String variableName, String variableType, CommentLine commentLine) {
this.variableName = variableName;
this.variableNameLower = variableName.toLowerCase();
this.variableType = variableType;
this.variableTypeLower = variableType.toLowerCase();
this.commentLine = commentLine;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EnvironmentComment that)) return false;
return Objects.equals(variableNameLower, that.variableNameLower) && Objects.equals(variableTypeLower, that.variableTypeLower);
}

@Override
public int hashCode() {
return Objects.hash(variableNameLower, variableTypeLower);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package me.blvckbytes.bbconfigmapper.preprocessor;

public enum InputConflict {
INVALID_KEY_CHARACTERS,
BLANK_VALUE,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package me.blvckbytes.bbconfigmapper.preprocessor;

import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.Writer;
import java.util.List;

public class KeyValueLine implements PreProcessorLine {

public final String content;
public final String key;
public final String value;
public final List<CommentLine> attachedComments;

// This line-number won't be synchronized when migrating - it's only supposed to be
// accessed on the file migrating *from*, as to calculate reference-distance metrics
public final int lineNumberAsRead;

public @Nullable KeyValueLine previous;
public @Nullable KeyValueLine next;

public boolean hadBlankBefore;
public boolean hadBlankAfter;

public KeyValueLine(
String content, String key, String value,
List<CommentLine> attachedComments,
int lineNumberAsRead
) {
this.content = content;
this.key = key;
this.value = value;
this.attachedComments = attachedComments;
this.lineNumberAsRead = lineNumberAsRead;
}

@Override
public void append(Writer writer) throws IOException {
for (var attachedComment : attachedComments) {
attachedComment.append(writer);
writer.write('\n');
}

writer.write(content);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package me.blvckbytes.bbconfigmapper.preprocessor;

public enum PreProcessConflict {
VARIABLE_NOT_FOUND,
MALFORMED_TEMPORARY_VARIABLE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package me.blvckbytes.bbconfigmapper.preprocessor;

import me.blvckbytes.bbconfigmapper.YamlConfig;
import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.nodes.*;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class PreProcessor {

@FunctionalInterface
public interface ScalarNodeHandler {
void handle(ScalarNode node) throws Exception;
}

private final Field scalarNodeValueField;

public PreProcessor() throws Exception {
this.scalarNodeValueField = ScalarNode.class.getDeclaredField("value");
this.scalarNodeValueField.setAccessible(true);
}

public String preProcess(String input, PreProcessorInput substitutions) {
var result = new StringBuilder();

var contentBegin = -1;
var substitutionBegin = -1;

for (var i = 0; i < input.length(); ++i) {
var currentChar = input.charAt(i);

if (currentChar == '@' && i != input.length() - 1 && input.charAt(i + 1) == '{') {
if (contentBegin >= 0) {
result.append(input, contentBegin, i);
contentBegin = -1;
}

substitutionBegin = i;
continue;
}

if (substitutionBegin >= 0 && currentChar == '}') {
var substitutionContent = input.substring(substitutionBegin + 2, i);
var openingParenthesisIndex = substitutionContent.indexOf('(');
var temporaryVariables = Map.<String, String>of();

if (openingParenthesisIndex >= 0) {
var closingParenthesisIndex = substitutionContent.indexOf(')');

if (closingParenthesisIndex > openingParenthesisIndex) {
var temporaryVariablesContent = substitutionContent.substring(openingParenthesisIndex + 1, closingParenthesisIndex);
temporaryVariables = parseTemporaryVariables(temporaryVariablesContent, openingParenthesisIndex + 1);
substitutionContent = substitutionContent.substring(0, openingParenthesisIndex).trim();
}
}

var substitutionKey = substitutionContent;
String substitution = substitutions.getValue(substitutionKey);

if (substitution == null)
throw new PreProcessorException(substitutionBegin, PreProcessConflict.VARIABLE_NOT_FOUND);

result.append(renderInterpolations(substitution, temporaryVariables));
substitutionBegin = -1;
continue;
}

if (substitutionBegin >= 0)
continue;

if (contentBegin == -1)
contentBegin = i;
}

if (contentBegin >= 0)
result.append(input.substring(contentBegin));

return result.toString();
}

public Map<String, String> parseTemporaryVariables(String input, int beginIndex) {
var result = new HashMap<String, String>();

var assignments = input.split(";");

for (var assignment : assignments) {
var assignmentParts = assignment.split("=", 2);

if (assignmentParts.length != 2)
throw new PreProcessorException(beginIndex, PreProcessConflict.MALFORMED_TEMPORARY_VARIABLE);

var key = assignmentParts[0].strip().toLowerCase();
var value = assignmentParts[1].strip();

if (key.isBlank() || value.isBlank())
throw new PreProcessorException(beginIndex, PreProcessConflict.MALFORMED_TEMPORARY_VARIABLE);

result.put(key, value);
}

return result;
}

public void forEachScalarValue(YamlConfig config, ScalarNodeHandler handler) throws Exception {
forEachScalarValue(config.getRootNode(), null, config.getExpressionMarkerSuffix(), handler);
}

public void setScalarValue(ScalarNode node, String value) throws Exception {
this.scalarNodeValueField.set(node, value);
}

public void forEachScalarValue(
Node valueNode, @Nullable ScalarNode keyNode,
String expressionMarkerSuffix,
ScalarNodeHandler handler
) throws Exception {
if (valueNode instanceof ScalarNode scalarNode) {
handler.handle(scalarNode);

if (keyNode != null && !keyNode.getValue().endsWith(expressionMarkerSuffix))
setScalarValue(keyNode, keyNode.getValue() + expressionMarkerSuffix);

return;
}

if (valueNode instanceof MappingNode mappingNode) {
for (var entry : mappingNode.getValue()) {
if (!(entry.getKeyNode() instanceof ScalarNode currentKeyNode))
continue;

forEachScalarValue(
entry.getValueNode(), currentKeyNode,
expressionMarkerSuffix,
handler
);
}

return;
}

if (valueNode instanceof SequenceNode sequenceNode) {
for (var entry : sequenceNode.getValue()) {
forEachScalarValue(
entry, keyNode,
expressionMarkerSuffix,
handler
);
}
}
}

private boolean isValidSubstitutionChar(char c) {
return (
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '_' || c == '-'
);
}

public String renderInterpolations(String input, Map<String, String> temporaryVariables) {
var result = new StringBuilder();

var stringBuffer = new StringBuilder();
var interpolationBuffer = new StringBuilder();

var wasPreviousCharBackslash = false;

for (var i = 0; i < input.length(); ++i) {
var currentChar = input.charAt(i);

var isEscaped = wasPreviousCharBackslash;
wasPreviousCharBackslash = currentChar == '\\';

if (currentChar == '{') {
if (isEscaped)
stringBuffer.setLength(stringBuffer.length() - 1);
else {
interpolationBuffer.append(currentChar);
continue;
}
}

if (!interpolationBuffer.isEmpty()) {
if (currentChar != '}') {
if (!isValidSubstitutionChar(currentChar)) {
stringBuffer.append(interpolationBuffer);
stringBuffer.append(currentChar);
interpolationBuffer.setLength(0);
continue;
}

interpolationBuffer.append(currentChar);
continue;
}

if (!stringBuffer.isEmpty()) {
if (!result.isEmpty())
result.append(" & ");

result.append('"').append(stringBuffer).append('"').append(" & ");
stringBuffer.setLength(0);
}

var substitution = interpolationBuffer.substring(1); // Leading '{' of begin-marker
var temporaryValue = temporaryVariables.get(substitution.toLowerCase());

if (temporaryValue != null)
substitution = temporaryValue;

result.append(substitution);

interpolationBuffer.setLength(0);
continue;
}

if (currentChar == '"')
stringBuffer.append('\\');

stringBuffer.append(currentChar);
}

// Unterminated interpolation - treat as string
if (!interpolationBuffer.isEmpty())
stringBuffer.append(interpolationBuffer);

if (!stringBuffer.isEmpty()) {
if (!result.isEmpty())
result.append(" & ");

result.append('"').append(stringBuffer).append('"');
}

return result.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.blvckbytes.bbconfigmapper.preprocessor;

public class PreProcessorException extends RuntimeException {

public final int charIndex;
public final PreProcessConflict conflict;

public PreProcessorException(int charIndex, PreProcessConflict conflict) {
this.charIndex = charIndex;
this.conflict = conflict;
}
}
Loading

0 comments on commit d67d55e

Please sign in to comment.