parse with antlr (#2747)

* grammar and libs

* gitignore

* parsing

* allow empty line as root statement

* tolerate #if

* config def changes

* s

* ant build

* workaround

* compiled tool

* console should not build config def tool

* ugh the dependency tree is insane

* this should fix hw ci

* console build

* console jenkins script

* test

* Revert "test"

This reverts commit 73f2da50f990bee9b51a1f919e8fcc6b06327d9a.

* throw on parse failure

* jar

* fix #2821

* comment
This commit is contained in:
Matthew Kennedy 2021-06-16 14:07:05 -07:00 committed by GitHub
parent 5e97d7e11f
commit 5ae4af23fe
34 changed files with 1074 additions and 7 deletions

View File

@ -20,6 +20,10 @@ jobs:
- name: Install Tools
run: sudo apt-get install ncftp
- name: Generate Java (Antlr)
working-directory: ./java_tools/configuration_definition
run: ant antlr
- name: Build console
working-directory: ./java_console
run: ant server_jar

View File

@ -47,6 +47,10 @@ jobs:
working-directory: ./firmware
run: openocd -f "interface/stlink.cfg" -f "target/stm32f4x.cfg" -c init -c targets -c "reset halt" -c "flash erase_sector 0 0 11" -c "flash write_image "build/rusefi.bin" 0x08000000" -c "reset run" -c "shutdown"
- name: Generate Java (Antlr)
working-directory: ./java_tools/configuration_definition
run: ant antlr
# This both compiles and runs HW CI tests
- name: Run Hardware CI
working-directory: ./java_console

View File

@ -47,6 +47,10 @@ jobs:
working-directory: ./firmware
run: openocd -f "interface/stlink.cfg" -f "target/stm32f4x.cfg" -c init -c targets -c "reset halt" -c "flash erase_sector 0 0 11" -c "flash write_image "build/rusefi.bin" 0x08000000" -c "reset run" -c "shutdown"
- name: Generate Java (Antlr)
working-directory: ./java_tools/configuration_definition
run: ant antlr
# This both compiles and runs HW CI tests
- name: Run Hardware CI
working-directory: ./java_console

View File

@ -87,7 +87,7 @@ struct_no_prefix engine_configuration_s
#define CAM_INPUTS_COUNT @@BANKS_COUNT@@ * @@CAMS_PER_BANK@@
#define CAM_INPUTS_COUNT_padding 0
// https://github.com/rusefi/rusefi/issues/2010 shows the corner case wheel with huge depth requirement
! https://github.com/rusefi/rusefi/issues/2010 shows the corner case wheel with huge depth requirement
#define GAP_TRACKING_LENGTH 18
#define SERVO_COUNT 8
@ -569,8 +569,8 @@ int16_t tpsErrorDetectionTooHigh;+TPS error detection: what throttle % is unreal
cranking_parameters_s cranking
float primingSquirtDurationMs;;"*C", 1, 0, -40, 200, 1
float ignitionDwellForCrankingMs;+Dwell duration while cranking;"ms", 1, 0, 0, 200, 1
uint16_t etbRevLimitStart;+Once engine speed passes this value, start reducing ETB angle.;1, 0, 0, 15000, 0
uint16_t etbRevLimitRange;+This far above 'Soft limiter start', fully close the throttle. At the bottom of the range, throttle control is normal. At the top of the range, the throttle is fully closed.;1, 0, 0, 2000, 0
uint16_t etbRevLimitStart;+Once engine speed passes this value, start reducing ETB angle.;"rpm",1, 0, 0, 15000, 0
uint16_t etbRevLimitRange;+This far above 'Soft limiter start', fully close the throttle. At the bottom of the range, throttle control is normal. At the top of the range, the throttle is fully closed.;"rpm",1, 0, 0, 2000, 0
MAP_sensor_config_s map;@see hasMapSensor\n@see isMapAveragingEnabled
@ -1954,7 +1954,8 @@ end_struct
#define ENGINE_SNIFFER_UNIT_US 10
! These commands are used exclusively by the rusEfi console
#define TS_TEST_COMMAND 't' // 0x74
! 0x74
#define TS_TEST_COMMAND 't'
#define TS_SD_R_COMMAND 'r'
#define TS_SD_W_COMMAND 'w'

View File

@ -3,7 +3,7 @@
<property name="hw_tests" value="build_hw"/>
<property name="jar_file" value="${jar_file_folder}/rusefi_console.jar"/>
<property name="server_jar_file" value="${jar_file_folder}/rusefi_server.jar"/>
<property name="lib_list" value="lib/mockito-all-1.10.19.jar:../java_tools/configuration_definition/lib/snakeyaml.jar:lib/log4j-api-2.13.3.jar:lib/log4j-core-2.13.3.jar:lib/jsr305-2.0.1.jar:lib/dfu/dfu_java.jar:lib/dfu/IntelHexParser.jar:lib/json-simple-1.1.1.jar:lib/server/javax.json.jar:lib/server/cactoos.jar:lib/server/takes.jar:lib/json-simple-1.1.1.jar:lib/jaxb-api.jar:lib/httpclient.jar:lib/httpmime.jar:lib/httpcore.jar:lib/jSerialComm.jar:lib/jcip-annotations-1.0.jar:lib/jlatexmath-1.0.6.jar:lib/swing-layout-1.0.jar:lib/jep.jar:lib/log4j.jar:lib/junit.jar:lib/SteelSeries-3.9.30.jar:lib/annotations.jar:lib/miglayout-4.0.jar:lib/surfaceplotter-2.0.1.jar"/>
<property name="lib_list" value="lib/mockito-all-1.10.19.jar:../java_tools/configuration_definition/lib/snakeyaml.jar:lib/log4j-api-2.13.3.jar:lib/log4j-core-2.13.3.jar:lib/jsr305-2.0.1.jar:lib/dfu/dfu_java.jar:lib/dfu/IntelHexParser.jar:lib/json-simple-1.1.1.jar:lib/server/javax.json.jar:lib/server/cactoos.jar:lib/server/takes.jar:lib/json-simple-1.1.1.jar:lib/jaxb-api.jar:lib/httpclient.jar:lib/httpmime.jar:lib/httpcore.jar:lib/jSerialComm.jar:lib/jcip-annotations-1.0.jar:lib/jlatexmath-1.0.6.jar:lib/swing-layout-1.0.jar:lib/jep.jar:lib/log4j.jar:lib/junit.jar:lib/SteelSeries-3.9.30.jar:lib/annotations.jar:lib/miglayout-4.0.jar:lib/surfaceplotter-2.0.1.jar:../java_tools/configuration_definition/lib/antlr-4.5-complete.jar"/>
<target name="clean">
<delete dir="build"/>

Binary file not shown.

View File

@ -1,2 +1,3 @@
out/
rusefi_tool.log
.antlr

View File

@ -0,0 +1,146 @@
grammar RusefiConfigGrammar;
@header {
package com.rusefi.generated;
}
// ...be generous in line endings...
ENDL: ('\n' | '\r\n' | '\r');
LINE_COMMENT: '!' ~[\r\n]* -> skip;
WS: [ \t]+ -> skip ;
// Special tokens need highest priority
Struct: 'struct';
StructNoPrefix: 'struct_no_prefix';
EndStruct: 'end_struct';
Definition: '#define';
Unused: 'unused';
Custom: 'custom';
Datatype: (('S'|'U')('08'|'16'|'32')) | 'F32';
Iterate: 'iterate';
Bits: 'bits';
Bit: 'bit';
Array: 'array';
Scalar: 'scalar';
FsioVisible: 'fsio_visible';
ArrayDimensionSeparator: 'x';
MUL: '*';
DIV: '/';
ADD: '+';
SUB: '-';
IntegerChars: [-]?[0-9]+;
FloatChars: IntegerChars [.] ([0-9]+)?;
IdentifierChars : [a-zA-Z_]([a-zA-Z0-9_]*);
// TODO: do we need replacementIdent AND identifier to be here?
replacementIdent: '@@' IdentifierChars '@@' | identifier;
String: [a-zA-Z_0-9.']+;
// match a quote, then anything not a quote, then another quote
QuotedString: '"' ~'"'* '"';
// legacy, remove me!
SemicolonedSuffix: ';' ~([;] | '\n')*;
SemicolonedString: SemicolonedSuffix ';';
integer: IntegerChars;
floatNum: FloatChars | IntegerChars;
expr
: floatNum # EvalNumber
| '{' expr '}' # EvalParens
| expr MUL expr # EvalMul
| expr DIV expr # EvalDiv
| expr ADD expr # EvalAdd
| expr SUB expr # EvalSub
| replacementIdent # EvalReplacement
;
numexpr: expr;
identifier: IdentifierChars | 'offset' | 'ArrayDimension';
restOfLine
: ~ENDL*
| 'true'
| 'false';
definition
: Definition identifier numexpr
| Definition identifier restOfLine;
struct: (Struct | StructNoPrefix) identifier ('@brief' restOfLine)? ENDL+ statements EndStruct;
fieldOption
: ('min' | 'max' | 'scale' | 'offset' | ) ':' numexpr
| 'digits' ':' integer
| ('unit' | 'comment') ':' QuotedString
;
fieldOptionsList
: '(' fieldOption? (',' fieldOption)* ')'
// TODO: why does the next line have a comma?
| /* legacy! */ (',' | SemicolonedString) (QuotedString ',' numexpr ',' numexpr ',' numexpr ',' numexpr ',' /*digits =*/integer)?
| /* legacy! */ SemicolonedSuffix
;
arrayLengthSpec: numexpr (ArrayDimensionSeparator numexpr)?;
scalarField: identifier FsioVisible? identifier (fieldOptionsList)?;
arrayField: identifier '[' arrayLengthSpec Iterate? ']' identifier SemicolonedString? (fieldOptionsList)?;
bitField: Bit identifier (',' QuotedString ',' QuotedString)? ('(' 'comment' ':' QuotedString ')')? SemicolonedSuffix?;
field
: scalarField
| arrayField
| bitField
;
// Indicates X bytes of free space
unusedField: Unused integer;
enumVal: QuotedString | integer;
enumRhs
: replacementIdent
| enumVal (',' enumVal)*
;
enumTypedefSuffix: /*ignored*/integer Bits ',' Datatype ',' '@OFFSET@' ',' '[' integer ':' integer ']' ',' enumRhs ;
scalarTypedefSuffix: /*ignored*/integer Scalar ',' Datatype ',' '@OFFSET@' fieldOptionsList ;
arrayTypedefSuffix: /*ignored*/arrayLengthSpec Array ',' Datatype ',' '@OFFSET@' ',' '[' arrayLengthSpec ']' fieldOptionsList;
stringTypedefSuffix: /*ignored*/replacementIdent 'string' ',' 'ASCII' ',' '@OFFSET@' ',' numexpr;
typedef: Custom identifier (enumTypedefSuffix | scalarTypedefSuffix | arrayTypedefSuffix | stringTypedefSuffix);
// Root statement is allowed to appear in the root of the file
rootStatement
: definition
| struct
| typedef
// TODO: remove me, and build multi-lambda-table in to the language
| ('#if' | '#else' | '#endif') restOfLine
| // empty line counts as a statement
;
rootStatements
: (rootStatement ENDL+)*
;
// Statements are allowed to appear inside a struct
statement
: rootStatement
| field /* tolerate trailing semicolon */ (';')?
| unusedField
;
statements
: (statement ENDL+)+
;
content: rootStatements EOF;

View File

@ -8,6 +8,10 @@
<delete dir="build"/>
</target>
<target name="antlr">
<java jar="lib/antlr-4.5-complete.jar" args="-o src/com/rusefi/generated RusefiConfigGrammar.g4" fork="true" />
</target>
<target name="compile">
<mkdir dir="build/classes"/>
<javac
@ -15,7 +19,7 @@
debug="true"
target="${javac.target}"
destdir="build/classes"
classpath="${console_path}/lib/jsr305-2.0.1.jar:lib/junit.jar:../../java_console/lib/annotations.jar:lib/snakeyaml.jar">
classpath="${console_path}/lib/jsr305-2.0.1.jar:lib/junit.jar:../../java_console/lib/annotations.jar:lib/snakeyaml.jar:lib/antlr-4.5-complete.jar">
<src path="src"/>
<src path="${console_path}/autoupdate/src"/>
<src path="${console_path}/inifile/src"/>
@ -34,7 +38,7 @@
<jvmarg value="-ea"/>
<jvmarg value="-XX:+HeapDumpOnOutOfMemoryError"/>
<formatter type="brief"/>
<classpath path="build/classes:lib/junit.jar:lib/annotations.jar:lib/snakeyaml.jar"/>
<classpath path="build/classes:lib/junit.jar:lib/annotations.jar:lib/snakeyaml.jar:lib/antlr-4.5-complete.jar"/>
<batchtest todir="build">
<fileset dir="src" includes="**/test/**/*Test.java"/>
<fileset dir="../enum_to_string/src" includes="**/test/**/*Test.java"/>
@ -51,6 +55,7 @@
</manifest>
<zipfileset dir="build/classes" includes="**/*.class"/>
<zipfileset src="lib/snakeyaml.jar" includes="**/*.class"/>
<zipfileset src="lib/antlr-4.5-complete.jar" includes="**/*.class"/>
</jar>
</target>

View File

@ -14,5 +14,14 @@
<orderEntry type="module" module-name="inifile" />
<orderEntry type="module" module-name="shared_io" />
<orderEntry type="module" module-name="models" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/antlr-4.5-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>

View File

@ -0,0 +1 @@
generated/

View File

@ -1,7 +1,14 @@
package com.rusefi;
import com.rusefi.generated.*;
import com.rusefi.newparse.ParseState;
import com.rusefi.newparse.parsing.Definition;
import com.rusefi.output.*;
import com.rusefi.util.*;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import com.rusefi.enum_reader.Value;
import org.yaml.snakeyaml.Yaml;
@ -241,6 +248,46 @@ public class ConfigDefinition {
processYamls(VariableRegistry.INSTANCE, yamlFiles, state);
}
// Parse the input files
{
ParseState listener = new ParseState();
// First process yaml files
//processYamls(listener, yamlFiles);
// First load prepend files
{
// Ignore duplicates of definitions made during prepend phase
listener.setDefinitionPolicy(Definition.OverwritePolicy.IgnoreNew);
for (String prependFile : prependFiles) {
// TODO: fix signature define file parsing
//parseFile(listener, prependFile);
}
}
// Now load the main config file
{
// don't allow duplicates in the main file
listener.setDefinitionPolicy(Definition.OverwritePolicy.NotAllowed);
parseFile(listener, definitionInputFile);
}
// Write C structs
// PrintStream cPrintStream = new PrintStream(new FileOutputStream(destCHeaderFileName));
// for (Struct s : listener.getStructs()) {
// StructLayout sl = new StructLayout(0, "root", s);
// sl.writeCLayoutRoot(cPrintStream);
// }
// cPrintStream.close();
// Write tunerstudio layout
// PrintStream tsPrintStream = new PrintStream(new FileOutputStream(tsPath + "/test.ini"));
// StructLayout root = new StructLayout(0, "root", listener.getLastStruct());
// root.writeTunerstudioLayout(tsPrintStream, new StructNamePrefixer());
// tsPrintStream.close();
}
BufferedReader definitionReader = new BufferedReader(new InputStreamReader(new FileInputStream(definitionInputFile), IoUtils.CHARSET.name()));
List<ConfigurationConsumer> destinations = new ArrayList<>();
@ -554,4 +601,48 @@ public class ConfigDefinition {
return c.getValue();
}
public static class RusefiParseErrorStrategy extends DefaultErrorStrategy {
private boolean hadError = false;
public boolean hadError() {
return this.hadError;
}
@Override
public void recover(Parser recognizer, RecognitionException e) {
this.hadError = true;
super.recover(recognizer, e);
}
@Override
public Token recoverInline(Parser recognizer) throws RecognitionException {
this.hadError = true;
return super.recoverInline(recognizer);
}
}
private static void parseFile(ParseState listener, String filePath) throws FileNotFoundException, IOException {
SystemOut.println("Parsing file (Antlr) " + filePath);
CharStream in = new ANTLRInputStream(new FileInputStream(filePath));
long start = System.nanoTime();
RusefiConfigGrammarParser parser = new RusefiConfigGrammarParser(new CommonTokenStream(new RusefiConfigGrammarLexer(in)));
RusefiParseErrorStrategy errorStrategy = new RusefiParseErrorStrategy();
parser.setErrorHandler(errorStrategy);
ParseTree tree = parser.content();
new ParseTreeWalker().walk(listener, tree);
double durationMs = (System.nanoTime() - start) / 1e6;
if (errorStrategy.hadError()) {
throw new RuntimeException("Parse failed, see error output above!");
}
SystemOut.println("Successfully parsed " + filePath + " in " + durationMs + "ms");
}
}

View File

@ -0,0 +1,475 @@
package com.rusefi.newparse;
import com.rusefi.generated.RusefiConfigGrammarBaseListener;
import com.rusefi.generated.RusefiConfigGrammarParser;
import com.rusefi.newparse.parsing.*;
import jdk.nashorn.internal.runtime.regexp.joni.constants.StringType;
import java.util.*;
import java.util.stream.Collectors;
public class ParseState extends RusefiConfigGrammarBaseListener {
Map<String, Definition> definitions = new HashMap<>();
Map<String, Struct> structs = new HashMap<>();
List<Struct> structList = new ArrayList<>();
Map<String, Typedef> typedefs = new HashMap<>();
public List<Struct> getStructs() {
return structList;
}
class Scope {
public List<Field> structFields = new ArrayList<>();
}
Scope scope = null;
Stack<Scope> scopes = new Stack<>();
@Override
public void exitContent(RusefiConfigGrammarParser.ContentContext ctx) {
assert(this.scopes.empty());
assert(this.scope == null);
assert(this.typedefName == null);
assert(evalResults.isEmpty());
assert(evalStack.empty());
}
private Definition.OverwritePolicy definitionOverwritePolicy = Definition.OverwritePolicy.NotAllowed;
public void addDefinition(String name, String value, Definition.OverwritePolicy overwritePolicy) {
Definition existingDefinition = definitions.getOrDefault(name, null);
if (existingDefinition != null) {
switch (existingDefinition.overwritePolicy) {
case NotAllowed:
throw new IllegalStateException("Tried to add definition for " + name + ", but one already existed.");
case Replace:
definitions.remove(existingDefinition);
case IgnoreNew:
// ignore the new definition, do nothing
return;
}
}
definitions.put(name, new Definition(name, value, overwritePolicy));
}
public void setDefinitionPolicy(Definition.OverwritePolicy policy) {
this.definitionOverwritePolicy = policy;
}
@Override
public void exitDefinition(RusefiConfigGrammarParser.DefinitionContext ctx) {
String name = ctx.identifier().getText();
String value;
if (!this.evalResults.isEmpty()) {
value = this.evalResults.remove().toString();
} else {
// glue the list of definitions back together
value = ctx.restOfLine().getText();
}
addDefinition(name, value, this.definitionOverwritePolicy);
}
String typedefName = null;
@Override
public void enterTypedef(RusefiConfigGrammarParser.TypedefContext ctx) {
this.typedefName = ctx.identifier().getText();
}
@Override
public void exitTypedef(RusefiConfigGrammarParser.TypedefContext ctx) {
this.typedefName = null;
}
@Override
public void exitScalarTypedefSuffix(RusefiConfigGrammarParser.ScalarTypedefSuffixContext ctx) {
Type datatype = Type.findByTsType(ctx.Datatype().getText());
FieldOptions options = new FieldOptions();
handleFieldOptionsList(options, ctx.fieldOptionsList());
this.typedefs.put(this.typedefName, new ScalarTypedef(this.typedefName, datatype, options));
}
@Override
public void enterEnumTypedefSuffix(RusefiConfigGrammarParser.EnumTypedefSuffixContext ctx) {
int startBit = Integer.parseInt(ctx.integer(1).getText());
int endBit = Integer.parseInt(ctx.integer(2).getText());
Type datatype = Type.findByTsType(ctx.Datatype().getText());
String values = ctx.enumRhs().getText();
// TODO: many enum defs are missing so this doesn't work yet
/*
if (values.startsWith("@@")) {
Definition def = this.definitions.get(values.replaceAll("@", ""));
if (def == null) {
throw new RuntimeException("couldn't find definition for " + values);
}
values = def.value;
}*/
this.typedefs.put(this.typedefName, new EnumTypedef(this.typedefName, datatype, startBit, endBit, values));
}
@Override
public void exitArrayTypedefSuffix(RusefiConfigGrammarParser.ArrayTypedefSuffixContext ctx) {
int arrayLength = this.arrayDim;
Type datatype = Type.findByTsType(ctx.Datatype().getText());
FieldOptions options = new FieldOptions();
handleFieldOptionsList(options, ctx.fieldOptionsList());
this.typedefs.put(this.typedefName, new ArrayTypedef(this.typedefName, arrayLength, datatype, options));
}
@Override
public void exitStringTypedefSuffix(RusefiConfigGrammarParser.StringTypedefSuffixContext ctx) {
Float stringLength = this.evalResults.remove();
this.typedefs.put(this.typedefName, new StringTypedef(this.typedefName, stringLength.intValue()));
}
@Override
public void enterStruct(RusefiConfigGrammarParser.StructContext ctx) {
// If we're already inside a struct, push that context on to the stack
if (scope != null) {
scopes.push(scope);
}
// Create new scratch space for this scope
scope = new Scope();
}
void handleFieldOptionsList(FieldOptions options, RusefiConfigGrammarParser.FieldOptionsListContext ctx) {
// Null means no options were configured, use defaults
if (ctx == null) {
return;
}
if (ctx.fieldOption().size() == 0) {
if (ctx.SemicolonedString() != null) {
options.comment = ctx.SemicolonedString().getText();
} else if (ctx.SemicolonedSuffix() != null) {
options.comment = ctx.SemicolonedSuffix().getText();
} else {
options.comment = "";
}
// this is a legacy field option list, parse it as such
if (!ctx.numexpr().isEmpty()) {
options.units = ctx.QuotedString().getText();
options.scale = evalResults.remove();
options.offset = evalResults.remove();
options.min = evalResults.remove();
options.max = evalResults.remove();
options.digits = Integer.parseInt(ctx.integer().getText());
// we should have consumed everything on the results list
assert(evalResults.size() == 0);
}
return;
}
for (RusefiConfigGrammarParser.FieldOptionContext fo : ctx.fieldOption()) {
String key = fo.getChild(0).getText();
String sValue = fo.getChild(2).getText();
if (key.equals("unit")) {
options.units = sValue;
} else if (key.equals("comment")) {
options.comment = sValue;
} else if (key.equals("digits")) {
options.digits = Integer.parseInt(sValue);
} else {
Float value = evalResults.remove();
switch (key) {
case "min": options.min = value; break;
case "max": options.max = value; break;
case "scale": options.scale = value; break;
case "offset": options.offset = value; break;
}
}
}
// we should have consumed everything on the results list
assert(evalResults.size() == 0);
}
@Override
public void exitScalarField(RusefiConfigGrammarParser.ScalarFieldContext ctx) {
String type = ctx.identifier(0).getText();
String name = ctx.identifier(1).getText();
// First check if this is an instance of a struct
if (structs.containsKey(type)) {
scope.structFields.add(new StructField(structs.get(type), name));
return;
}
// Check first if we have a typedef for this type
Typedef typedef = this.typedefs.get(type);
FieldOptions options = null;
if (typedef != null) {
if (typedef instanceof ScalarTypedef) {
ScalarTypedef scTypedef = (ScalarTypedef)typedef;
// Copy the typedef's options list - we don't want to edit it
options = scTypedef.options.copy();
// Switch to the "real" type, that is the typedef's type
type = scTypedef.type.cType;
} else if (typedef instanceof ArrayTypedef) {
ArrayTypedef arTypedef = (ArrayTypedef) typedef;
// Copy the typedef's options list - we don't want to edit it
options = arTypedef.options.copy();
// Merge the read-in options list with the default from the typedef (if exists)
handleFieldOptionsList(options, ctx.fieldOptionsList());
ScalarField prototype = new ScalarField(arTypedef.type, name, options);
scope.structFields.add(new ArrayField<ScalarField>(prototype, arTypedef.length, false));
return;
} else if (typedef instanceof EnumTypedef) {
EnumTypedef bTypedef = (EnumTypedef) typedef;
options = new FieldOptions();
// Merge the read-in options list with the default from the typedef (if exists)
handleFieldOptionsList(options, ctx.fieldOptionsList());
scope.structFields.add(new EnumField(bTypedef.type, type, name, bTypedef.values, options));
return;
} else if (typedef instanceof StringTypedef) {
StringTypedef sTypedef = (StringTypedef) typedef;
scope.structFields.add(new StringField(name, sTypedef.size));
return;
} else {
// TODO: throw
}
} else {
// If no typedef found, it MUST be a native type
if (!Type.findByCtype(type).isPresent()) {
throw new RuntimeException("didn't understand type " + type + " for element " + name);
}
// no typedef found, create new options list
options = new FieldOptions();
}
// Merge the read-in options list with the default from the typedef (if exists)
handleFieldOptionsList(options, ctx.fieldOptionsList());
scope.structFields.add(new ScalarField(Type.findByCtype(type).get(), name, options));
}
@Override
public void enterBitField(RusefiConfigGrammarParser.BitFieldContext ctx) {
String name = ctx.identifier().getText();
// Check if there's already a bit group at the end of the current struct
BitGroup group = null;
if (!scope.structFields.isEmpty()) {
Object lastElement = scope.structFields.get(scope.structFields.size() - 1);
if (lastElement instanceof BitGroup) {
group = (BitGroup)lastElement;
}
}
// there was no group, create and add it
if (group == null) {
group = new BitGroup();
scope.structFields.add(group);
}
String comment = ctx.SemicolonedSuffix() == null ? null : ctx.SemicolonedSuffix().getText();
group.addBitField(new BitField(name, comment));
}
@Override
public void exitArrayField(RusefiConfigGrammarParser.ArrayFieldContext ctx) {
String type = ctx.identifier(0).getText();
String name = ctx.identifier(1).getText();
int length = this.arrayDim;
// check if the iterate token is present
boolean iterate = ctx.Iterate() != null;
// First check if this is an array of structs
if (structs.containsKey(type)) {
// iterate required for structs
assert(iterate);
scope.structFields.add(new ArrayField<StructField>(new StructField(structs.get(type), name), length, iterate));
return;
}
// Check first if we have a typedef for this type
Typedef typedef = this.typedefs.get(type);
FieldOptions options = null;
if (typedef != null) {
if (typedef instanceof ScalarTypedef) {
ScalarTypedef scTypedef = (ScalarTypedef) typedef;
// Copy the typedef's options list - we don't want to edit it
options = scTypedef.options.copy();
// Switch to the "real" type, that is the typedef's type
type = scTypedef.type.cType;
} else if (typedef instanceof EnumTypedef) {
EnumTypedef bTypedef = (EnumTypedef) typedef;
options = new FieldOptions();
handleFieldOptionsList(options, ctx.fieldOptionsList());
EnumField prototype = new EnumField(bTypedef.type, type, name, bTypedef.values, options);
scope.structFields.add(new ArrayField<EnumField>(prototype, length, iterate));
return;
} else if (typedef instanceof StringTypedef) {
StringTypedef sTypedef = (StringTypedef) typedef;
// iterate required for strings
assert(iterate);
StringField prototype = new StringField(name, sTypedef.size);
scope.structFields.add(new ArrayField<StringField>(prototype, length, iterate));
return;
} else {
throw new RuntimeException("didn't understand type " + type + " for element " + name);
}
} else {
// If no typedef found, it MUST be a native type
if (!Type.findByCtype(type).isPresent()) {
throw new RuntimeException("didn't understand type " + type + " for element " + name);
}
// no typedef found, create new options list
options = new FieldOptions();
}
// Merge the read-in options list with the default from the typedef (if exists)
handleFieldOptionsList(options, ctx.fieldOptionsList());
ScalarField prototype = new ScalarField(Type.findByCtype(type).get(), name, options);
scope.structFields.add(new ArrayField(prototype, length, iterate));
}
private int arrayDim = 0;
@Override
public void exitArrayLengthSpec(RusefiConfigGrammarParser.ArrayLengthSpecContext ctx) {
arrayDim = evalResults.remove().intValue();
if (ctx.ArrayDimensionSeparator() != null) {
arrayDim *= evalResults.remove().intValue();
}
}
@Override
public void enterUnusedField(RusefiConfigGrammarParser.UnusedFieldContext ctx) {
scope.structFields.add(new UnusedField(Integer.parseInt(ctx.integer().getText())));
}
private Struct lastStruct = null;
public Struct getLastStruct() {
return lastStruct;
}
@Override
public void exitStruct(RusefiConfigGrammarParser.StructContext ctx) {
String structName = ctx.identifier().getText();
assert(scope != null);
assert(scope.structFields != null);
String comment = ctx.restOfLine() == null ? null : ctx.restOfLine().getText().toString();
Struct s = new Struct(structName, scope.structFields, ctx.StructNoPrefix() != null, comment);
structs.put(structName, s);
structList.add(s);
lastStruct = s;
// We're leaving with this struct, re-apply the next struct out so more fields can be added to it
if (scopes.empty()) {
scope = null;
} else {
scope = scopes.pop();
}
}
private Stack<Float> evalStack = new Stack<>();
@Override
public void exitEvalNumber(RusefiConfigGrammarParser.EvalNumberContext ctx) {
evalStack.push(Float.parseFloat(ctx.floatNum().getText()));
}
@Override
public void exitEvalReplacement(RusefiConfigGrammarParser.EvalReplacementContext ctx) {
// Strip any @@ symbols
String defName = ctx.getText().replaceAll("@", "");
if (!this.definitions.containsKey(defName)) {
throw new RuntimeException("Definition not found for " + ctx.getText());
}
// Find the matching definition, parse it, and push on to the eval stack
evalStack.push(Float.parseFloat(this.definitions.get(defName).value));
}
@Override
public void exitEvalMul(RusefiConfigGrammarParser.EvalMulContext ctx) {
Float right = evalStack.pop();
Float left = evalStack.pop();
evalStack.push(left * right);
}
@Override
public void exitEvalDiv(RusefiConfigGrammarParser.EvalDivContext ctx) {
Float right = evalStack.pop();
Float left = evalStack.pop();
evalStack.push(left / right);
}
@Override
public void exitEvalAdd(RusefiConfigGrammarParser.EvalAddContext ctx) {
Float right = evalStack.pop();
Float left = evalStack.pop();
evalStack.push(left + right);
}
@Override
public void exitEvalSub(RusefiConfigGrammarParser.EvalSubContext ctx) {
Float right = evalStack.pop();
Float left = evalStack.pop();
evalStack.push(left - right);
}
private Queue<Float> evalResults = new LinkedList<>();
@Override
public void exitNumexpr(RusefiConfigGrammarParser.NumexprContext ctx) {
assert(evalStack.size() == 1);
evalResults.add(evalStack.pop());
}
}

View File

@ -0,0 +1,13 @@
package com.rusefi.newparse.parsing;
public class ArrayField<PrototypeType extends PrototypeField> implements Field {
public final int length;
public final Boolean iterate;
public final PrototypeType prototype;
public ArrayField(PrototypeType prototype, int length, Boolean iterate) {
this.length = length;
this.iterate = iterate;
this.prototype = prototype;
}
}

View File

@ -0,0 +1,15 @@
package com.rusefi.newparse.parsing;
public class ArrayTypedef extends Typedef {
public final FieldOptions options;
public final Type type;
public final int length;
public ArrayTypedef(String name, int length, Type type, FieldOptions options) {
super(name);
this.length = length;
this.type = type;
this.options = options;
}
}

View File

@ -0,0 +1,16 @@
package com.rusefi.newparse.parsing;
public class BitField {
public final String name;
public final String comment;
public BitField(String name, String comment) {
this.name = name;
this.comment = comment;
}
@Override
public String toString() {
return "BitField: " + this.name;
}
}

View File

@ -0,0 +1,17 @@
package com.rusefi.newparse.parsing;
import java.util.ArrayList;
import java.util.List;
public class BitGroup implements Field {
public final List<BitField> bitFields = new ArrayList<>();
public void addBitField(BitField b) {
bitFields.add(b);
}
@Override
public String toString() {
return "BitGroup: " + bitFields.size() + " bits";
}
}

View File

@ -0,0 +1,19 @@
package com.rusefi.newparse.parsing;
public class Definition {
public final String name;
public final String value;
public final OverwritePolicy overwritePolicy;
public enum OverwritePolicy {
NotAllowed,
Replace,
IgnoreNew
}
public Definition(String name, String value, OverwritePolicy overwritePolicy) {
this.name = name;
this.value = value;
this.overwritePolicy = overwritePolicy;
}
}

View File

@ -0,0 +1,22 @@
package com.rusefi.newparse.parsing;
public class EnumField extends PrototypeField {
public final Type type;
public final String enumType;
public final String values;
public final FieldOptions options;
public EnumField(Type type, String enumType, String name, String values, FieldOptions options) {
super(name);
this.type = type;
this.enumType = enumType;
this.values = values;
this.options = options;
}
@Override
public String toString() {
return "enum " + type.cType + " " + this.name;
}
}

View File

@ -0,0 +1,17 @@
package com.rusefi.newparse.parsing;
public class EnumTypedef extends Typedef {
public final Type type;
public final int startBit;
public final int endBit;
public final String values;
public EnumTypedef(String name, Type type, int startBit, int endBit, String values) {
super(name);
this.type = type;
this.startBit = startBit;
this.endBit = endBit;
this.values = values;
}
}

View File

@ -0,0 +1,4 @@
package com.rusefi.newparse.parsing;
public interface Field {
}

View File

@ -0,0 +1,52 @@
package com.rusefi.newparse.parsing;
import java.io.PrintStream;
public class FieldOptions {
public float min;
public float max;
public float scale;
public float offset;
public int digits;
public String units;
public String comment;
public FieldOptions() {
min = 0;
max = 0;
scale = 1;
offset = 0;
digits = 0;
units = "\"\"";
comment = "";
}
// Produce a deep copy of this object
public FieldOptions copy() {
FieldOptions other = new FieldOptions();
other.min = this.min;
other.max = this.max;
other.scale = this.scale;
other.offset = this.offset;
other.digits = this.digits;
other.units = this.units;
other.comment = this.comment;
return other;
}
public void printTsFormat(PrintStream ps) {
ps.print(units);
ps.print(", ");
ps.print(scale);
ps.print(", ");
ps.print(offset);
ps.print(", ");
ps.print(min);
ps.print(", ");
ps.print(max);
ps.print(", ");
ps.print(digits);
}
}

View File

@ -0,0 +1,9 @@
package com.rusefi.newparse.parsing;
public abstract class PrototypeField implements Field {
public final String name;
protected PrototypeField(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,18 @@
package com.rusefi.newparse.parsing;
public class ScalarField extends PrototypeField {
public final Type type;
public final FieldOptions options;
public ScalarField(Type type, String name, FieldOptions options) {
super(name);
this.type = type;
this.options = options;
}
@Override
public String toString() {
return type.cType + " " + name;
}
}

View File

@ -0,0 +1,13 @@
package com.rusefi.newparse.parsing;
public class ScalarTypedef extends Typedef {
public final FieldOptions options;
public final Type type;
public ScalarTypedef(String name, Type type, FieldOptions options) {
super(name);
this.options = options;
this.type = type;
}
}

View File

@ -0,0 +1,11 @@
package com.rusefi.newparse.parsing;
public class StringField extends PrototypeField{
public final int size;
public StringField(String name, int size) {
super(name);
this.size = size;
}
}

View File

@ -0,0 +1,12 @@
package com.rusefi.newparse.parsing;
public class StringTypedef extends Typedef {
public final int size;
public StringTypedef(String name, int size) {
super(name);
this.size = size;
}
}

View File

@ -0,0 +1,18 @@
package com.rusefi.newparse.parsing;
import java.io.PrintStream;
import java.util.List;
public class Struct implements Field {
public final String name;
public final Boolean noPrefix;
public final List<Field> fields;
public final String comment;
public Struct(String name, List<Field> fields, boolean noPrefix, String comment) {
this.name = name;
this.noPrefix = noPrefix;
this.fields = fields;
this.comment = comment;
}
}

View File

@ -0,0 +1,11 @@
package com.rusefi.newparse.parsing;
public class StructField extends PrototypeField {
public final Struct struct;
public StructField(Struct struct, String name) {
super(name);
this.struct = struct;
}
}

View File

@ -0,0 +1,36 @@
package com.rusefi.newparse.parsing;
import java.util.Arrays;
import java.util.Optional;
public enum Type {
U08("uint8_t", "U08", 1),
S08("int8_t", "S08", 1),
U16("uint16_t", "U16", 2),
S16("int16_t", "S16", 2),
U32("uint32_t", "U32", 4),
S32("int32_t", "S32", 4),
F32("float", "F32", 4),
ANGLE_T("angle_t", "F32", 4),
// TODO: remove I32
I32("int", "S32", 4);
public final String cType;
public final String tsType;
public final int size;
Type(String cType, String tunerstudioType, int size) {
this.cType = cType;
this.tsType = tunerstudioType;
this.size = size;
}
public static Optional<Type> findByCtype(String cType) {
return Arrays.stream(Type.values()).filter(t -> t.cType.equals(cType)).findFirst();
}
public static Type findByTsType(String tsType) {
return Arrays.stream(Type.values()).filter(t -> t.tsType.equals(tsType)).findFirst().get();
}
}

View File

@ -0,0 +1,9 @@
package com.rusefi.newparse.parsing;
public class Typedef {
public final String name;
protected Typedef (String name) {
this.name = name;
}
}

View File

@ -0,0 +1,9 @@
package com.rusefi.newparse.parsing;
public class UnusedField implements Field {
public final int size;
public UnusedField(int size) {
this.size = size;
}
}

View File

@ -3,6 +3,11 @@
echo "java version"
java -version
echo "Generating Java (Antlr)"
cd java_tools/configuration_definition
ant antlr
cd ../..
echo "Building java console"
pwd
cd java_console