diff --git a/java_tools/ConfigDefinition.jar b/java_tools/ConfigDefinition.jar index 382f7dfc55..7c3b08900e 100644 Binary files a/java_tools/ConfigDefinition.jar and b/java_tools/ConfigDefinition.jar differ diff --git a/java_tools/configuration_definition/RusefiConfigGrammar.g4 b/java_tools/configuration_definition/RusefiConfigGrammar.g4 index c60cf11f5f..e3f140cafb 100644 --- a/java_tools/configuration_definition/RusefiConfigGrammar.g4 +++ b/java_tools/configuration_definition/RusefiConfigGrammar.g4 @@ -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 diff --git a/java_tools/configuration_definition/src/main/java/com/rusefi/ConfigDefinition.java b/java_tools/configuration_definition/src/main/java/com/rusefi/ConfigDefinition.java index 459c18480c..bd59ea6216 100644 --- a/java_tools/configuration_definition/src/main/java/com/rusefi/ConfigDefinition.java +++ b/java_tools/configuration_definition/src/main/java/com/rusefi/ConfigDefinition.java @@ -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) { /** diff --git a/java_tools/configuration_definition/src/main/java/com/rusefi/RusefiParseErrorStrategy.java b/java_tools/configuration_definition/src/main/java/com/rusefi/RusefiParseErrorStrategy.java index 609e84a127..47ce16e05c 100644 --- a/java_tools/configuration_definition/src/main/java/com/rusefi/RusefiParseErrorStrategy.java +++ b/java_tools/configuration_definition/src/main/java/com/rusefi/RusefiParseErrorStrategy.java @@ -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() { diff --git a/java_tools/configuration_definition/src/main/java/com/rusefi/newparse/ParseState.java b/java_tools/configuration_definition/src/main/java/com/rusefi/newparse/ParseState.java index d95623b157..8a237db617 100644 --- a/java_tools/configuration_definition/src/main/java/com/rusefi/newparse/ParseState.java +++ b/java_tools/configuration_definition/src/main/java/com/rusefi/newparse/ParseState.java @@ -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 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 evalStack = new Stack<>(); - + private final Stack evalStack = new Stack<>(); @Override public void exitEvalNumber(RusefiConfigGrammarParser.EvalNumberContext ctx) { @@ -615,9 +636,9 @@ public class ParseState { } }; - }; + } static class Scope { - public List structFields = new ArrayList<>(); + public final List structFields = new ArrayList<>(); } } diff --git a/java_tools/configuration_definition/src/main/java/com/rusefi/newparse/parsing/FieldOptions.java b/java_tools/configuration_definition/src/main/java/com/rusefi/newparse/parsing/FieldOptions.java index 576ea2255f..b73db0563b 100644 --- a/java_tools/configuration_definition/src/main/java/com/rusefi/newparse/parsing/FieldOptions.java +++ b/java_tools/configuration_definition/src/main/java/com/rusefi/newparse/parsing/FieldOptions.java @@ -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 diff --git a/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/NewParseHelper.java b/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/NewParseHelper.java new file mode 100644 index 0000000000..d18b2c42c3 --- /dev/null +++ b/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/NewParseHelper.java @@ -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; + } +} diff --git a/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/ParseDefinitionsTest.java b/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/ParseDefinitionsTest.java new file mode 100644 index 0000000000..c3e2de2cf7 --- /dev/null +++ b/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/ParseDefinitionsTest.java @@ -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"); + } +} diff --git a/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/ParseStructTest.java b/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/ParseStructTest.java new file mode 100644 index 0000000000..3ccd5b98b5 --- /dev/null +++ b/java_tools/configuration_definition/src/test/java/com/rusefi/test/newParse/ParseStructTest.java @@ -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 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()); + } +}