mirror of https://github.com/rusefi/rusefi.git
463 lines
14 KiB
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());
|
|
}
|
|
}
|
|
|