actually run the new parser (#4218)

* let the new parser run

* s

* ant

* minor cleanups

* allow passing string

* tests found a genuine bug

* test definitions

* helper

* test lots of parsing

* test missing define

* test typedefs, bits

* fix bug with more than 32 consecutive bits overflowing a single field

* s

* test extra hex definitions

* jar
This commit is contained in:
Matthew Kennedy 2022-05-31 05:38:23 -07:00 committed by GitHub
parent c71a293f90
commit 289a01fd05
9 changed files with 586 additions and 47 deletions

Binary file not shown.

View File

@ -8,6 +8,7 @@ grammar RusefiConfigGrammar;
ENDL: ('\n' | '\r\n' | '\r');
LINE_COMMENT: '!' ~[\r\n]* -> skip;
LINE_COMMENT2: '//' ~[\r\n]* -> skip;
WS: [ \t]+ -> skip ;
// Special tokens need highest priority

View File

@ -1,6 +1,7 @@
package com.rusefi;
import com.rusefi.newparse.ParseState;
import com.rusefi.newparse.parsing.Definition;
import com.rusefi.output.*;
import com.rusefi.trigger.TriggerWheelTSLogic;
import com.rusefi.util.SystemOut;
@ -189,11 +190,8 @@ public class ConfigDefinition {
new TriggerWheelTSLogic().execute(triggersFolder, state.variableRegistry);
/*
// Parse the input files
{
// Load prepend files
{
// Ignore duplicates of definitions made during prepend phase
@ -219,7 +217,6 @@ public class ConfigDefinition {
// TsWriter writer = new TsWriter();
// writer.writeTunerstudio(parseState, tsPath + "/rusefi.input", tsPath + "/" + TSProjectConsumer.TS_FILE_OUTPUT_NAME);
}
*/
if (tsOutputsDestination != null) {
/**

View File

@ -17,13 +17,31 @@ import java.io.IOException;
public class RusefiParseErrorStrategy extends DefaultErrorStrategy {
private boolean hadError = false;
static void parseDefinitionFile(ParseTreeListener listener, String filePath) throws IOException {
public static void parseDefinitionFile(ParseTreeListener listener, String filePath) throws IOException {
SystemOut.println("Parsing file (Antlr) " + filePath);
CharStream in = new ANTLRInputStream(new FileInputStream(filePath));
long start = System.nanoTime();
parse(listener, in);
double durationMs = (System.nanoTime() - start) / 1e6;
SystemOut.println("Successfully parsed (Antlr) " + filePath + " in " + durationMs + "ms");
}
public static void parseDefinitionString(ParseTreeListener listener, String content) throws IOException {
SystemOut.println("Parsing string (Antlr)");
CharStream in = new ANTLRInputStream(content);
long start = System.nanoTime();
parse(listener, in);
double durationMs = (System.nanoTime() - start) / 1e6;
SystemOut.println("Successfully parsed (Antlr) in " + durationMs + "ms");
}
private static void parse(ParseTreeListener listener, CharStream in) {
RusefiConfigGrammarParser parser = new RusefiConfigGrammarParser(new CommonTokenStream(new RusefiConfigGrammarLexer(in)));
RusefiParseErrorStrategy errorStrategy = new RusefiParseErrorStrategy();
@ -31,13 +49,10 @@ public class RusefiParseErrorStrategy extends DefaultErrorStrategy {
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");
}
public boolean hadError() {

View File

@ -29,6 +29,10 @@ public class ParseState {
private Struct lastStruct = null;
public ParseState() {
this.enumsReader = null;
}
public ParseState(EnumsReader enumsReader) {
this.enumsReader = enumsReader;
@ -51,7 +55,7 @@ public class ParseState {
addDefinition(name, value);
// Also add ints as 16b hex
addDefinition(name + "_16_hex", String.format("\\\\x%02x\\\\x%02x", (value >> 8) & 0xFF, value & 0xFF));
addDefinition(name + "_16_hex", String.format("\\x%02x\\x%02x", (value >> 8) & 0xFF, value & 0xFF));
}
private static boolean isNumeric(String str) {
@ -68,6 +72,10 @@ public class ParseState {
}
private String[] resolveEnumValues(String enumName) {
if (this.enumsReader == null) {
return new String[0];
}
TreeMap<Integer, String> valueNameById = new TreeMap<>();
EnumsReader.EnumState stringValueMap = this.enumsReader.getEnums().get(enumName);
@ -82,7 +90,7 @@ public class ParseState {
} else {
Definition def = this.definitions.get(value.getValue());
if (def == null)
throw new IllegalStateException("No value for " + value);;
throw new IllegalStateException("No value for " + value);
valueNameById.put((Integer)def.value, value.getName());
}
}
@ -112,7 +120,8 @@ public class ParseState {
case NotAllowed:
throw new IllegalStateException("Tried to add definition for " + name + ", but one already existed.");
case Replace:
definitions.remove(existingDefinition);
definitions.remove(name);
break;
case IgnoreNew:
// ignore the new definition, do nothing
return;
@ -135,18 +144,11 @@ public class ParseState {
@Override
public void exitContent(RusefiConfigGrammarParser.ContentContext ctx) {
if (!scopes.empty())
throw new IllegalStateException();
if (scope != null)
throw new IllegalStateException();
if (typedefName != null)
throw new IllegalStateException();
if (!evalResults.isEmpty())
throw new IllegalStateException();
if (!evalStack.empty())
throw new IllegalStateException();
assert(scopes.empty());
assert(scope == null);
assert(typedefName == null);
assert(evalResults.isEmpty());
assert(evalStack.empty());
}
@Override
@ -159,11 +161,11 @@ public class ParseState {
addDefinition(name, Double.parseDouble(ctx.floatNum().getText()));
} else if (ctx.numexpr() != null) {
double evalResult = evalResults.remove();
double floored = Math.floor(evalResult);
int floored = (int)Math.floor(evalResult);
if (Math.abs(floored - evalResult) < 0.001) {
// value is an int, process as such
handleIntDefinition(name, (int)floored);
handleIntDefinition(name, floored);
} else {
// Value is a double, add it
addDefinition(name, evalResult);
@ -312,21 +314,34 @@ public class ParseState {
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 {
Double value = evalResults.remove();
switch (key) {
case "unit":
options.units = sValue;
break;
case "comment":
options.comment = sValue;
break;
case "digits":
options.digits = Integer.parseInt(sValue);
break;
default:
Double value = evalResults.remove();
switch (key) {
case "min": options.min = value.floatValue(); break;
case "max": options.max = value.floatValue(); break;
case "scale": options.scale = value.floatValue(); break;
case "offset": options.offset = value.floatValue(); break;
}
switch (key) {
case "min":
options.min = value.floatValue();
break;
case "max":
options.max = value.floatValue();
break;
case "scale":
options.scale = value.floatValue();
break;
case "offset":
options.offset = value.floatValue();
break;
}
break;
}
}
@ -401,6 +416,11 @@ public class ParseState {
if (lastElement instanceof BitGroup) {
group = (BitGroup)lastElement;
// If this group is full, create a new one instead of continuing on here.
if (group.bitFields.size() == 32) {
group = null;
}
}
}
@ -432,6 +452,10 @@ public class ParseState {
boolean iterate = ctx.Iterate() != null;
boolean autoscale = ctx.Autoscale() != null;
if (iterate && length.length != 1) {
throw new IllegalStateException("Cannot iterate multi dimensional array: " + name);
}
// First check if this is an array of structs
if (structs.containsKey(type)) {
// iterate required for structs
@ -515,9 +539,8 @@ public class ParseState {
String structName = ctx.identifier().getText();
assert(scope != null);
assert(scope.structFields != null);
String comment = ctx.restOfLine() == null ? null : ctx.restOfLine().getText().toString();
String comment = ctx.restOfLine() == null ? null : ctx.restOfLine().getText();
Struct s = new Struct(structName, scope.structFields, ctx.StructNoPrefix() != null, comment);
structs.put(structName, s);
@ -535,7 +558,6 @@ public class ParseState {
@Override
public void exitUnionField(RusefiConfigGrammarParser.UnionFieldContext ctx) {
assert(scope != null);
assert(scope.structFields != null);
// unions must have at least 1 member
assert(!scope.structFields.isEmpty());
@ -549,8 +571,7 @@ public class ParseState {
scope.structFields.add(u);
}
private Stack<Double> evalStack = new Stack<>();
private final Stack<Double> evalStack = new Stack<>();
@Override
public void exitEvalNumber(RusefiConfigGrammarParser.EvalNumberContext ctx) {
@ -615,9 +636,9 @@ public class ParseState {
}
};
};
}
static class Scope {
public List<Field> structFields = new ArrayList<>();
public final List<Field> structFields = new ArrayList<>();
}
}

View File

@ -36,7 +36,7 @@ public class FieldOptions {
return other;
}
private static String tryRound(float value) {
public static String tryRound(float value) {
int intVal = Math.round(value);
// If the rounded value can exactly represent this float, then print as an integer

View File

@ -0,0 +1,14 @@
package com.rusefi.test.newParse;
import com.rusefi.RusefiParseErrorStrategy;
import com.rusefi.newparse.ParseState;
import java.io.IOException;
public class NewParseHelper {
public static ParseState parse(String input) throws IOException {
ParseState parseState = new ParseState();
RusefiParseErrorStrategy.parseDefinitionString(parseState.getListener(), input);
return parseState;
}
}

View File

@ -0,0 +1,147 @@
package com.rusefi.test.newParse;
import com.rusefi.RusefiParseErrorStrategy;
import com.rusefi.newparse.ParseState;
import com.rusefi.newparse.parsing.Definition;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import static com.rusefi.test.newParse.NewParseHelper.parse;
public class ParseDefinitionsTest {
@Test
public void basicIntegerDefinition() throws IOException {
ParseState state = parse("#define foo 123");
Assert.assertNull(state.findDefinition("notFoo"));
Definition def = state.findDefinition("foo");
Assert.assertTrue(def.isNumeric());
Assert.assertEquals(123, def.asDouble(), 1e-5);
Definition defHex = state.findDefinition("foo_16_hex");
Assert.assertFalse(defHex.isNumeric());
Assert.assertEquals("\\x00\\x7b", defHex.value);
}
@Test
public void basicDoubleDefinition() throws IOException {
ParseState state = parse("#define foo 123.5");
Definition def = state.findDefinition("foo");
Assert.assertTrue(def.isNumeric());
Assert.assertEquals(123.5, def.asDouble(), 1e-5);
}
@Test
public void basicStringDefinition() throws IOException {
ParseState state = parse("#define foo \"fooValue\"");
Definition def = state.findDefinition("foo");
Assert.assertFalse(def.isNumeric());
Assert.assertTrue(def.value instanceof String);
Assert.assertEquals("\"fooValue\"", def.value);
}
@Test
public void definitionWithNumexprValue() throws IOException {
ParseState state = parse("#define foo 5 + 8 * 2 + 3");
Definition def = state.findDefinition("foo");
Assert.assertTrue(def.isNumeric());
Assert.assertEquals(24, def.asDouble(), 1e-5);
}
@Test
public void definitionWithAnotherDefinition() throws IOException {
ParseState state = parse(
"#define val1 20\n" +
"#define val2 val1 + 1\n" +
"#define val3 3 * @@val2@@");
Definition def1 = state.findDefinition("val1");
Assert.assertTrue(def1.isNumeric());
Assert.assertEquals(20, def1.asDouble(), 1e-5);
Definition def2 = state.findDefinition("val2");
Assert.assertTrue(def2.isNumeric());
Assert.assertEquals(21, def2.asDouble(), 1e-5);
Definition def3 = state.findDefinition("val3");
Assert.assertTrue(def3.isNumeric());
Assert.assertEquals(63, def3.asDouble(), 1e-5);
}
@Test(expected = RuntimeException.class)
public void definitionEvalMissingDefinition() throws IOException {
parse(
"#define val1 20\n" +
"#define val2 val1 + 1\n" +
"#define val3 3 * valgggggg"
);
}
@Test(expected = RuntimeException.class)
public void definitionEvalNonNumeric() throws IOException {
parse(
"#define val1 \"foo\"\n" +
"#define val2 val1 + 1"
);
}
@Test
public void definitionOverwritePolicyReplace() throws IOException {
ParseState state = new ParseState();
state.setDefinitionPolicy(Definition.OverwritePolicy.Replace);
RusefiParseErrorStrategy.parseDefinitionString(state.getListener(), "#define val 20");
Definition def = state.findDefinition("val");
Assert.assertEquals(20, def.asDouble(), 1e-5);
// Now parse another definition with the same name
RusefiParseErrorStrategy.parseDefinitionString(state.getListener(), "#define val 40");
// Should get back the new definition, not the old one
Definition def2 = state.findDefinition("val");
Assert.assertTrue(def != def2);
Assert.assertEquals(40, def2.asDouble(), 1e-5);
}
@Test
public void definitionOverwritePolicyIgnoreNew() throws IOException {
ParseState state = new ParseState();
state.setDefinitionPolicy(Definition.OverwritePolicy.IgnoreNew);
RusefiParseErrorStrategy.parseDefinitionString(state.getListener(), "#define val 20");
Definition def = state.findDefinition("val");
Assert.assertEquals(20, def.asDouble(), 1e-5);
// Now parse another definition with the same name
state.setDefinitionPolicy(Definition.OverwritePolicy.IgnoreNew);
RusefiParseErrorStrategy.parseDefinitionString(state.getListener(), "#define val 40");
// New one ignored, still returns the same old one.
Assert.assertEquals(def, state.findDefinition("val"));
Assert.assertEquals(20, def.asDouble(), 1e-5);
}
@Test(expected = IllegalStateException.class)
public void definitionOverwritePolicyNotAllowed() throws IOException {
ParseState state = new ParseState();
state.setDefinitionPolicy(Definition.OverwritePolicy.NotAllowed);
RusefiParseErrorStrategy.parseDefinitionString(state.getListener(), "#define val 20");
Definition def = state.findDefinition("val");
Assert.assertEquals(20, def.asDouble(), 1e-5);
// Now parse another definition with the same name, this should throw
RusefiParseErrorStrategy.parseDefinitionString(state.getListener(), "#define val 40");
}
}

View File

@ -0,0 +1,344 @@
package com.rusefi.test.newParse;
import com.rusefi.RusefiParseErrorStrategy;
import com.rusefi.newparse.ParseState;
import com.rusefi.newparse.parsing.*;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.List;
import static com.rusefi.test.newParse.NewParseHelper.parse;
public class ParseStructTest {
@Test
public void structEmpty() throws IOException {
ParseState state = parse(
"struct myStruct\n\n" +
"end_struct\n"
);
// Parsed exactly one struct
Assert.assertEquals(1, state.getStructs().size());
Struct myStruct = state.getStructs().get(0);
// It's a real struct!
Assert.assertNotNull(myStruct);
// Check that it was parsed correctly
Assert.assertEquals("myStruct", myStruct.name);
Assert.assertEquals(0, myStruct.fields.size());
Assert.assertEquals(false, myStruct.noPrefix);
Assert.assertEquals(state.getStructs().get(0), state.getLastStruct());
}
@Test
public void structEmptyNoPrefix() throws IOException {
ParseState state = parse(
"struct_no_prefix myStruct\n\n" +
"end_struct\n"
);
Struct myStruct = state.getStructs().get(0);
// Check that this one is marked as no prefix
Assert.assertEquals(true, myStruct.noPrefix);
}
@Test
public void structMultiple() throws IOException {
ParseState state = parse(
"struct myStruct1\n\n" +
"end_struct\n" +
"struct myStruct2\n\n" +
"end_struct\n"
);
// Now there are two structs!
Assert.assertEquals(2, state.getStructs().size());
Assert.assertEquals("myStruct1", state.getStructs().get(0).name);
Assert.assertEquals("myStruct2", state.getStructs().get(1).name);
// Check that get last struct returns the last one
Assert.assertEquals(state.getStructs().get(1), state.getLastStruct());
}
@Test
public void structNested() throws IOException {
ParseState state = parse(
"struct myStructOuter\n" +
"struct myStructInner\n\n" +
"end_struct\n" +
"end_struct\n"
);
// Now there are two structs!
Assert.assertEquals(2, state.getStructs().size());
Assert.assertEquals("myStructInner", state.getStructs().get(0).name);
Assert.assertEquals("myStructOuter", state.getStructs().get(1).name);
// Check that get last struct returns the outer one
Assert.assertEquals(state.getStructs().get(1), state.getLastStruct());
}
@Test
public void structContainsStruct() throws IOException {
ParseState state = parse(
"struct myStruct\n\n" +
"end_struct\n" +
"struct myStruct2\n" +
"myStruct foo\n" +
"end_struct"
);
Assert.assertEquals(2, state.getStructs().size());
Struct inner = state.getStructs().get(0);
Assert.assertEquals(0, inner.fields.size());
// Check that the outer contains a struct
Struct outer = state.getStructs().get(1);
Assert.assertEquals(1, outer.fields.size());
Assert.assertTrue(outer.fields.get(0) instanceof StructField);
}
@Test(expected = RuntimeException.class)
public void structMissingStruct() throws IOException {
parse(
"struct myStruct2\n" +
"myStruct foo\n" +
"end_struct"
);
}
private static Field parseSingleField(String fieldLine) throws IOException {
ParseState state = parse(
"struct myStruct\n" +
fieldLine + "\n" +
"end_struct\n"
);
List<Field> fields = state.getLastStruct().fields;
Assert.assertEquals(1, fields.size());
return fields.get(0);
}
@Test
public void scalarOptions() throws IOException {
Field f = parseSingleField("int foo;comment;\"units\", 1, 2, 3, 4, 5");
ScalarField sf = (ScalarField) f;
FieldOptions opt = sf.options;
Assert.assertEquals("comment", opt.comment);
Assert.assertEquals("\"units\"", opt.units);
Assert.assertEquals(1, opt.scale, 0.001f);
Assert.assertEquals(2, opt.offset, 0.001f);
Assert.assertEquals(3, opt.min, 0.001f);
Assert.assertEquals(4, opt.max, 0.001f);
Assert.assertEquals(5, opt.digits);
}
private static void checkType(String inputType, int size, String cType, String tsType) throws IOException {
ScalarField sf = (ScalarField) parseSingleField(inputType + " foo");
Assert.assertEquals("foo", sf.name);
Assert.assertEquals(false, sf.autoscale);
Assert.assertEquals(size, sf.type.size);
Assert.assertEquals(cType, sf.type.cType);
Assert.assertEquals(tsType, sf.type.tsType);
}
@Test
public void typeMappings() throws IOException {
checkType("int", 4, "int", "S32");
checkType("int8_t", 1, "int8_t", "S08");
checkType("int16_t", 2, "int16_t", "S16");
checkType("int32_t", 4, "int32_t", "S32");
checkType("uint8_t", 1, "uint8_t", "U08");
checkType("uint16_t", 2, "uint16_t", "U16");
checkType("uint32_t", 4, "uint32_t", "U32");
checkType("float", 4, "float", "F32");
}
@Test
public void arraySingleDimension() throws IOException {
Field f = parseSingleField("uint8_t[10] testBins");
Assert.assertTrue(f instanceof ArrayField);
ArrayField af = (ArrayField)f;
Assert.assertEquals(1, af.length.length);
Assert.assertEquals(10, af.length[0]);
PrototypeField prototype = af.prototype;
Assert.assertTrue(prototype instanceof ScalarField);
}
@Test
public void arrayMultiDimension() throws IOException {
Field f = parseSingleField("uint8_t[5 x 8] testBins");
Assert.assertTrue(f instanceof ArrayField);
ArrayField af = (ArrayField)f;
Assert.assertEquals(2, af.length.length);
Assert.assertEquals(5, af.length[0]);
Assert.assertEquals(8, af.length[1]);
}
@Test
public void arrayIterate() throws IOException {
Field f = parseSingleField("uint8_t[10 iterate] testBins");
Assert.assertTrue(f instanceof ArrayField);
ArrayField af = (ArrayField)f;
Assert.assertEquals(true, af.iterate);
}
@Test(expected = IllegalStateException.class)
public void checkMultiDimensionArrayIterateThrows() throws IOException {
parseSingleField("uint8_t[5 x 8 iterate] testBins");
}
@Test
public void arrayOfStructs() throws IOException {
ParseState state = parse(
"struct myStruct\n\n" +
"end_struct\n" +
"struct myStruct2\n" +
"myStruct[5 iterate] foo\n" +
"end_struct"
);
Assert.assertEquals(2, state.getStructs().size());
// Inner should be empty
Struct inner = state.getStructs().get(0);
Assert.assertEquals(0, inner.fields.size());
// Check that the outer contains an array of structs
Struct outer = state.getStructs().get(1);
Assert.assertEquals(1, outer.fields.size());
ArrayField af = (ArrayField)outer.fields.get(0);
// Check length and that it contains a struct
Assert.assertEquals(1, af.length.length);
Assert.assertEquals(5, af.length[0]);
Assert.assertTrue(af.prototype instanceof StructField);
}
@Test
public void typedefScalar() throws IOException {
ParseState ps = parse("custom myTypedef 4 scalar, F32, @OFFSET@, \"unit\", 1, 2, 3, 4, 5\n" +
"struct myStruct\n" +
"myTypedef foo;comment\n" +
"end_struct");
ScalarField sf = (ScalarField)ps.getLastStruct().fields.get(0);
Assert.assertEquals("foo", sf.name);
Assert.assertEquals(false, sf.autoscale);
Assert.assertEquals(4, sf.type.size);
Assert.assertEquals("float", sf.type.cType);
Assert.assertEquals("F32", sf.type.tsType);
FieldOptions opt = sf.options;
Assert.assertEquals("comment", opt.comment);
Assert.assertEquals("\"unit\"", opt.units);
Assert.assertEquals(1, opt.scale, 0.001f);
Assert.assertEquals(2, opt.offset, 0.001f);
Assert.assertEquals(3, opt.min, 0.001f);
Assert.assertEquals(4, opt.max, 0.001f);
Assert.assertEquals(5, opt.digits);
}
@Test
public void typedefString() throws IOException {
ParseState ps = parse("custom lua_script_t 1000 string, ASCII, @OFFSET@, 1000\n" +
"struct myStruct\n" +
"lua_script_t luaScript\n" +
"end_struct");
StringField sf = (StringField)ps.getLastStruct().fields.get(0);
Assert.assertEquals("luaScript", sf.name);
Assert.assertEquals(1000, sf.size);
}
@Test
public void unused() throws IOException {
ParseState state = parse(
"struct_no_prefix myStruct\n" +
"unused 27\n" +
"end_struct\n"
);
UnusedField uf = (UnusedField)state.getLastStruct().fields.get(0);
Assert.assertEquals(27, uf.size);
}
@Test
public void bitFieldsBasic() throws IOException {
ParseState state = parse(
"struct_no_prefix myStruct\n" +
"bit myBit1\n" +
"bit myBit2\n" +
"end_struct\n"
);
BitGroup bf = (BitGroup)state.getLastStruct().fields.get(0);
Assert.assertEquals(2, bf.bitFields.size());
Assert.assertEquals("myBit1", bf.bitFields.get(0).name);
Assert.assertEquals("myBit2", bf.bitFields.get(1).name);
}
@Test
public void bitFieldsAdvanced() throws IOException {
ParseState state = parse(
"struct_no_prefix myStruct\n" +
"bit myBit,\"a\",\"b\";comment\n" +
"end_struct\n"
);
BitGroup bg = (BitGroup)state.getLastStruct().fields.get(0);
BitField bf = bg.bitFields.get(0);
Assert.assertEquals("myBit", bf.name);
Assert.assertEquals("\"a\"", bf.trueValue);
Assert.assertEquals("\"b\"", bf.falseValue);
Assert.assertEquals("comment", bf.comment);
}
@Test
public void bitFieldsThirtyThreeBits() throws IOException {
StringBuilder input = new StringBuilder("struct myStruct\n");
for (int i = 0; i < 33; i++) {
input.append("bit myBit").append(i).append("\n");
}
input.append("end_struct\n");
ParseState state = parse(input.toString());
Assert.assertEquals(2, state.getLastStruct().fields.size());
Assert.assertEquals(32, ((BitGroup)state.getLastStruct().fields.get(0)).bitFields.size());
Assert.assertEquals(1, ((BitGroup)state.getLastStruct().fields.get(1)).bitFields.size());
}
}