rusefi/java_tools/configuration_definition_base/src/main/java/com/rusefi/ConfigFieldImpl.java

463 lines
14 KiB
Java

package com.rusefi;
import com.devexperts.logging.Logging;
import com.opensr5.ini.field.EnumIniField;
import com.rusefi.core.Pair;
import com.rusefi.output.ConfigStructure;
import com.rusefi.output.JavaFieldsConsumer;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.devexperts.logging.Logging.getLogging;
import com.rusefi.parse.TypesHelper;
import org.jetbrains.annotations.Nullable;
/**
* This is an immutable model of an individual field
* Andrey Belomutskiy, (c) 2013-2020
* 1/15/15
*/
public class ConfigFieldImpl implements ConfigField {
private static final Logging log = getLogging(ConfigFieldImpl.class);
public static final ConfigFieldImpl VOID = new ConfigFieldImpl(null, "", null, null, null, new int[0], null, false, false, null, null);
private static final String typePattern = "([\\w\\d_]+)(\\[([\\w\\d]+)(\\sx\\s([\\w\\d]+))?(\\s([\\w\\d]+))?\\])?";
private static final String namePattern = "[[\\w\\d\\s_]]+";
private static final String commentPattern = ";([^;]*)";
private static final Pattern FIELD = Pattern.compile(typePattern + "\\s(" + namePattern + ")(" + commentPattern + ")?(;(.*))?");
private static final Pattern DIRECTIVE = Pattern.compile("#(if\\s" + namePattern + "|else|elif\\s\" + namePattern + \"|endif)");
public static final String VOID_NAME = "";
public static final String BOOLEAN_T = "boolean";
public static final String DIRECTIVE_T = "directive";
private final String name;
private final String comment;
public final String arraySizeVariableName;
private final String type;
private final int[] arraySizes;
private final String tsInfo;
private final boolean isIterate;
private final ReaderState state;
private final boolean hasAutoscale;
private final String trueName;
private final String falseName;
private final ConfigStructure parent;
private boolean isFromIterate;
private String iterateOriginalName;
private int iterateIndex;
/**
* todo: one day someone should convert this into a builder
*/
public ConfigFieldImpl(ReaderState state,
String name,
String comment,
String arraySizeAsText,
String type,
int[] arraySizes,
@Nullable String tsInfo,
boolean isIterate,
boolean hasAutoscale,
String trueName,
String falseName) {
this.hasAutoscale = hasAutoscale;
this.trueName = trueName == null ? "true" : trueName;
this.falseName = falseName == null ? "false" : falseName;
Objects.requireNonNull(name, comment + " " + type);
assertNoWhitespaces(name);
this.name = name;
if (!isVoid())
Objects.requireNonNull(state);
this.state = state;
this.parent = state == null ? null : (state.isStackEmpty() ? null : state.peek());
this.comment = comment;
if (!isVoid())
Objects.requireNonNull(type);
this.type = type;
this.arraySizeVariableName = arraySizeAsText;
this.arraySizes = arraySizes;
this.tsInfo = tsInfo == null ? null : state.getVariableRegistry().applyVariables(tsInfo);
this.isIterate = isIterate;
if (tsInfo != null) {
String[] tokens = getTokens();
if (tokens.length > 1) {
String scale = tokens[1].trim();
Double scaleDouble;
try {
scaleDouble = Double.parseDouble(scale);
} catch (NumberFormatException ignore) {
scaleDouble = -1.0;
}
if (!hasAutoscale && scaleDouble != 1) {
throw new IllegalStateException("Unexpected scale of " + scale + " without autoscale on " + this);
}
}
}
}
@Override
public ConfigStructure getParentStructureType() {
return parent;
}
private static int getSize(VariableRegistry variableRegistry, String s) {
if (variableRegistry.intValues.containsKey(s)) {
return variableRegistry.intValues.get(s);
}
return Integer.parseInt(s);
}
@Override
public ConfigStructure getStructureType() {
return getState().getStructures().get(getTypeName());
}
@Override
public boolean isArray() {
return arraySizeVariableName != null || arraySizes.length != 0;
}
@Override
public String getArraySizeVariableName() {
return arraySizeVariableName;
}
@Override
public String getTrueName() {
return trueName;
}
@Override
public String getFalseName() {
return falseName;
}
@Override
public boolean isBit() {
return BOOLEAN_T.equalsIgnoreCase(type);
}
@Override
public boolean isDirective() {
return DIRECTIVE_T.equalsIgnoreCase(type);
}
private boolean isVoid() {
return name.equals(VOID_NAME);
}
public static void assertNoWhitespaces(String name) {
if (name.contains(" ") || name.contains("\t"))
throw new IllegalArgumentException("Invalid name: " + name);
}
/**
* @see ConfigFieldParserTest#testParseLine()
*/
public static ConfigFieldImpl parse(ReaderState state, String line) {
Matcher matcher = FIELD.matcher(line);
if (!matcher.matches())
return null;
String nameString = matcher.group(8).trim();
String[] nameTokens = nameString.split("\\s");
String name = nameTokens[nameTokens.length - 1];
boolean hasAutoscale = false;
for (String autoscaler : nameTokens) {
if (autoscaler.equals("autoscale")) {
hasAutoscale = true;
break;
}
}
String comment = matcher.group(10);
validateComment(comment);
String type = matcher.group(1);
int[] arraySizes;
String arraySizeAsText;
if (matcher.group(5) != null) {
arraySizeAsText = matcher.group(3) + "][" + matcher.group(5);
arraySizes = new int[2];
arraySizes[0] = getSize(state.getVariableRegistry(), matcher.group(3));
arraySizes[1] = getSize(state.getVariableRegistry(), matcher.group(5));
} else if (matcher.group(3) != null) {
arraySizeAsText = matcher.group(3);
arraySizes = new int[1];
arraySizes[0] = getSize(state.getVariableRegistry(), arraySizeAsText);
} else {
arraySizes = new int[0];
arraySizeAsText = null;
}
String tsInfo = matcher.group(12);
boolean isIterate = "iterate".equalsIgnoreCase(matcher.group(7));
ConfigFieldImpl field = new ConfigFieldImpl(state, name, comment, arraySizeAsText, type, arraySizes,
tsInfo, isIterate, hasAutoscale, null, null);
if (log.debugEnabled())
log.debug("type " + type);
if (log.debugEnabled())
log.debug("name " + name);
if (log.debugEnabled())
log.debug("comment " + comment);
return field;
}
private static void validateComment(String comment) {
if (comment == null)
return;
comment = comment.trim();
if (comment.isEmpty())
return;
if (comment.charAt(0) == '"' && !EnumIniField.isQuoted(comment))
throw new MaybeSemicolonWasMissedException("This comment looks like semicolon was missed: " + comment);
}
public static boolean isPreprocessorDirective(String line) {
Matcher matcher = DIRECTIVE.matcher(line);
return matcher.matches();
}
@Override
public int getSize(ConfigField next) {
if (isBit() && next.isBit()) {
// we have a protection from 33+ bits in a row in BitState, see BitState.TooManyBitsInARow
return 0;
}
if (isBit())
return 4;
int size = getElementSize();
for (int s : arraySizes) {
size *= s;
}
return size;
}
@Override
public String toString() {
return "ConfigField{" +
"name='" + name + '\'' +
", type='" + type + '\'' +
", arraySizes=" + Arrays.toString(arraySizes) +
'}';
}
@Override
public int[] getArraySizes() {
return arraySizes;
}
@Override
public String getComment() {
if (comment == null)
return null;
return comment.trim();
}
/**
* field name without structure name
*
* @see JavaFieldsConsumer#writeJavaFields prefix parameter for structure name
*/
@Override
public String getName() {
return name;
}
/**
* @see TypesHelper
*/
@Override
public String getTypeName() {
return type;
}
@Override
public int getElementSize() {
return isVoid() ? 0 : TypesHelper.getElementSize(state, type);
}
/**
* this property of array expands field into a bunch of variables like field1 field2 field3 etc
*/
@Override
public boolean isIterate() {
return isIterate;
}
@Override
public boolean isHasAutoscale() {
return hasAutoscale;
}
@Override
public ReaderState getState() {
return state;
}
@Override
public String getTsInfo() {
return tsInfo;
}
@Override
public String autoscaleSpec() {
Pair<Integer, Integer> pair = autoscaleSpecPair();
if (pair == null)
return null;
return pair.first + ", " + pair.second;
}
@Override
public double autoscaleSpecNumber() {
Pair<Integer, Integer> pair = autoscaleSpecPair();
if (pair == null)
return 1;
return 1.0 * pair.second / pair.first;
}
@Override
public Pair<Integer, Integer> autoscaleSpecPair() {
if (!hasAutoscale) {
return null;
}
if (tsInfo == null)
throw new IllegalArgumentException("tsInfo expected with autoscale: " + this);
String[] tokens = getTokens();
if (tokens.length < 2)
throw new IllegalArgumentException("Second comma-separated token expected in [" + tsInfo + "] for " + name);
String scale = tokens[1].trim();
double factor;
if (scale.startsWith("{") && scale.endsWith("}")) {
// Handle just basic division, not a full fledged eval loop
scale = scale.substring(1, scale.length() - 1);
String[] parts = scale.split("/");
if (parts.length != 2)
throw new IllegalArgumentException(name + ": Two parts of division expected in " + scale);
factor = Double.parseDouble(parts[0]) / Double.parseDouble(parts[1]);
} else {
factor = Double.parseDouble(scale);
}
int mul, div;
if (factor < 1.d) {
mul = (int) Math.round(1. / factor);
div = 1;
} else {
mul = 1;
div = (int) factor;
}
// Verify accuracy
double factor2 = ((double) div) / mul;
double accuracy = Math.abs((factor2 / factor) - 1.);
if (accuracy > 0.0000001) {
// Don't want to deal with exception propogation; this should adequately not compile
throw new IllegalStateException("$*@#$* Cannot accurately represent autoscale for " + tokens[1]);
}
return new Pair<>(mul, div);
}
private String[] getTokens() {
if (tsInfo == null)
return new String[0];
return tsInfo.split(",");
}
@Override
public String getUnits() {
String[] tokens = getTokens();
if (tokens.length == 0)
return "";
return unquote(tokens[0].trim());
}
@Override
public double getMin() {
String[] tokens = getTokens();
if (tokens.length < 4)
return -1;
return Double.parseDouble(tokens[3]);
}
@Override
public double getMax() {
String[] tokens = getTokens();
if (tokens.length < 5)
return -1;
return Double.parseDouble(tokens[4]);
}
@Override
public int getDigits() {
String[] tokens = getTokens();
if (tokens.length < 6)
return 0;
return Integer.parseInt(tokens[5].trim());
}
@Override
public String getCategory() {
String[] tokens = getTokens();
if (tokens.length < 7)
return null;
return tokens[6].trim();
}
// see testUnquote
public static String unquote(String token) {
return VariableRegistry.unquote(token);
}
public void setFromIterate(String iterateOriginalName, int iterateIndex) {
this.iterateOriginalName = iterateOriginalName;
this.iterateIndex = iterateIndex;
this.isFromIterate = true;
}
@Override
public String getIterateOriginalName() {
return iterateOriginalName;
}
@Override
public int getIterateIndex() {
return iterateIndex;
}
@Override
public boolean isFromIterate() {
return isFromIterate;
}
/**
* todo: find more usages for this method?
* @see DataLogConsumer.getComment
*/
public String getCommentOrName() {
if (comment == null || comment.trim().isEmpty())
return VariableRegistry.quote(name);
return comment;
}
@Override
public String getCommentTemplated() {
return state.getVariableRegistry().applyVariables(getComment());
}
}