* grammar and libs

* gitignore

* parsing

* allow empty line as root statement

* tolerate #if

* config def changes

* s

* ant build

* workaround

* compiled tool

* grammar for unions

* parse unions

* layout logic

* fix union alignment

* union in config

* jar

* comment

* jar

* jar

* no prefix on root struct

* stray space

* normalize some enums
This commit is contained in:
Matthew Kennedy 2021-06-18 09:31:23 -07:00 committed by GitHub
parent 94d7a413b2
commit d3d6c1694a
17 changed files with 882 additions and 10 deletions

View File

@ -66,7 +66,7 @@
! all the sub-structures are going to be nested within the primary structure, that's
! needed to get a proper TunerStudio file
struct persistent_config_s
struct_no_prefix persistent_config_s
struct_no_prefix engine_configuration_s
@ -1620,6 +1620,11 @@ lambda_table_t lambdaTable;
afr_table_t lambdaTable;
#endif
! union
! lambda_table_t lambdaTable
! afr_table_t afrTable
! end_union
float[FUEL_LOAD_COUNT] lambdaLoadBins;;"", 1, 0.0, 0, 500.0, 2
float[FUEL_RPM_COUNT] lambdaRpmBins;;"RPM", 1, 0.0, 0, 18000.0, 2

Binary file not shown.

View File

@ -95,10 +95,17 @@ scalarField: identifier FsioVisible? identifier (fieldOptionsList)?;
arrayField: identifier '[' arrayLengthSpec Iterate? ']' identifier SemicolonedString? (fieldOptionsList)?;
bitField: Bit identifier (',' QuotedString ',' QuotedString)? ('(' 'comment' ':' QuotedString ')')? SemicolonedSuffix?;
unionField: 'union' ENDL+ fields 'end_union';
field
: scalarField
| arrayField
| bitField
| unionField
;
fields
: (field ENDL+)+
;
// Indicates X bytes of free space

View File

@ -151,6 +151,13 @@ public class ParseState extends RusefiConfigGrammarBaseListener {
scope = new Scope();
}
@Override
public void enterUnionField(RusefiConfigGrammarParser.UnionFieldContext ctx) {
// Unions behave like a struct as far as scope is concerned (but is processed differently later
// to overlap all members, instead of placing them in sequence as in a struct)
enterStruct(null);
}
void handleFieldOptionsList(FieldOptions options, RusefiConfigGrammarParser.FieldOptionsListContext ctx) {
// Null means no options were configured, use defaults
if (ctx == null) {
@ -412,6 +419,23 @@ public class ParseState extends RusefiConfigGrammarBaseListener {
}
}
@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());
Union u = new Union(scope.structFields);
// Restore the containing scope
scope = scopes.pop();
// Lastly, add the union to the scope
scope.structFields.add(u);
}
private Stack<Float> evalStack = new Stack<>();

View File

@ -0,0 +1,32 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.*;
import java.io.PrintStream;
public class ArrayIterateScalarLayout extends ArrayLayout {
public ArrayIterateScalarLayout(PrototypeField prototype, int length) {
super(prototype, length);
}
private void emitOne(PrintStream ps, StructNamePrefixer prefixer, int offset, int idx) {
// Set element's position within the array
this.prototypeLayout.setOffset(offset + this.prototypeLayout.getSize() * idx);
// Put a 1-based index on the end of the name to distinguish in TS
prefixer.setSuffix(Integer.toString(idx + 1));
this.prototypeLayout.writeTunerstudioLayout(ps, prefixer);
prefixer.resetSuffix();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
// Time to iterate: emit one scalar per array element, with the name modified accordingly
for (int i = 0; i < length; i++) {
emitOne(ps, prefixer, this.offset, i);
}
}
// C layout is the same if iterated or not, use default implementation
}

View File

@ -0,0 +1,32 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.*;
import java.io.PrintStream;
public class ArrayIterateStructLayout extends ArrayLayout {
public ArrayIterateStructLayout(StructField prototype, int length) {
super(prototype, length);
}
private void emitOne(PrintStream ps, StructNamePrefixer prefixer, int offset, int idx) {
// Set element's position within the array
this.prototypeLayout.setOffset(offset + this.prototypeLayout.getSize() * idx);
// Put a 1-based index on the end of the name to distinguish in TS
prefixer.setSuffix(Integer.toString(idx + 1));
this.prototypeLayout.writeTunerstudioLayout(ps, prefixer);
prefixer.resetSuffix();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
// Time to iterate: emit one scalar per array element, with the name modified accordingly
for (int i = 0; i < this.length; i++) {
emitOne(ps, prefixer, this.offset, i);
}
}
// C layout is the same if iterated or not, use default implementation
}

View File

@ -0,0 +1,69 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.*;
import java.io.PrintStream;
public class ArrayLayout extends Layout {
protected final int length;
protected final Layout prototypeLayout;
public ArrayLayout(PrototypeField prototype, int length) {
this.length = length;
if (prototype instanceof ScalarField) {
prototypeLayout = new ScalarLayout((ScalarField)prototype);
} else if (prototype instanceof EnumField) {
prototypeLayout = new EnumLayout((EnumField) prototype);
} else if (prototype instanceof StringField) {
prototypeLayout = new StringLayout((StringField) prototype);
} else if (prototype instanceof StructField) {
StructField structPrototype = (StructField)prototype;
prototypeLayout = new StructLayout(0, prototype.name, structPrototype.struct);
} else {
throw new RuntimeException("unexpected field type during array layout");
}
}
@Override
public int getSize() {
return this.prototypeLayout.getSize() * this.length;
}
@Override
public int getAlignment() {
// Arrays only need to be aligned on the alignment of the element, not the whole array
return this.prototypeLayout.getAlignment();
}
@Override
public void setOffset(int offset) {
super.setOffset(offset);
this.prototypeLayout.setOffset(offset);
}
@Override
public void setOffsetWithinStruct(int offset) {
super.setOffsetWithinStruct(offset);
this.prototypeLayout.setOffsetWithinStruct(offset);
}
@Override
public String toString() {
return "Array of " + this.prototypeLayout.toString() + " length " + this.length + " " + super.toString();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
this.prototypeLayout.writeTunerstudioLayout(ps, prefixer, this.length);
}
@Override
public void writeCLayout(PrintStream ps) {
// Skip zero length arrays, they may be used for padding
if (this.length > 0) {
this.prototypeLayout.writeCLayout(ps, this.length);
}
}
}

View File

@ -0,0 +1,84 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.BitGroup;
import com.rusefi.newparse.parsing.EnumField;
import com.rusefi.newparse.parsing.Type;
import com.rusefi.newparse.parsing.UnusedField;
import java.io.PrintStream;
import java.util.List;
import java.util.stream.Collectors;
public class BitGroupLayout extends Layout {
private class BitLayout {
public final String name;
public final String comment;
public BitLayout(String name, String comment) {
this.name = name;
this.comment = comment;
}
}
private final List<BitLayout> bits;
public BitGroupLayout(BitGroup bitGroup) {
int size = bitGroup.bitFields.size();
if (size > 32) {
throw new RuntimeException("tried to create bit group starting with " + bitGroup.bitFields.get(0).name + " but it contained " + size + " which is more than the maximum of 32.");
}
this.bits = bitGroup.bitFields.stream().map(bf -> new BitLayout(bf.name, bf.comment)).collect(Collectors.toList());
}
@Override
public int getSize() {
return 4;
}
@Override
public String toString() {
return "Bit group " + super.toString();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
for (int i = 0; i < bits.size(); i++) {
BitLayout bit = bits.get(i);
ps.print(prefixer.get(bit.name));
ps.print(" = bits, U32, ");
ps.print(this.offset);
ps.print(", [");
ps.print(i + ":" + i);
// TODO: print actual bit options
ps.print("], \"false\", \"true\"");
ps.println();
}
}
@Override
public void writeCLayout(PrintStream ps) {
// always emit all 32 bits
for (int i = 0; i < 32; i++) {
ps.print("\t/**\n\t");
if (i < bits.size()) {
BitLayout bit = this.bits.get(i);
if (bit.comment != null) {
ps.println(" * " + bit.comment.replaceAll("[+]", "").replaceAll(";", "").replace("\\n", "\n\t * "));
ps.print('\t');
}
ps.println("offset " + this.offsetWithinStruct + " bit " + i + " */");
ps.println("\tbool " + bit.name + " : 1;");
} else {
// Force pad out all bit groups to a full 32b/4B
ps.println("offset " + this.offsetWithinStruct + " bit " + i + " */");
ps.println("\tbool unusedBit_" + this.offsetWithinStruct + "_" + i + " : 1;");
}
}
}
}

View File

@ -0,0 +1,58 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.EnumField;
import com.rusefi.newparse.parsing.FieldOptions;
import com.rusefi.newparse.parsing.Type;
import java.io.PrintStream;
public class EnumLayout extends Layout {
private final String name;
private final Type type;
private final String enumType;
private final String values;
private final FieldOptions options;
public EnumLayout(EnumField field) {
this.name = field.name;
this.type = field.type;
this.enumType = field.enumType;
this.values = field.values;
this.options = field.options;
}
@Override
public int getSize() {
return this.type.size;
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
ps.print(prefixer.get(this.name));
ps.print(" = bits, ");
ps.print(this.type.tsType);
ps.print(", ");
ps.print(this.offset);
ps.print(", ");
// TODO: automatically compute number of bits required?
ps.print("[0:7], ");
// TODO: where should value define resolution happen?
ps.print(this.values);
ps.println();
}
@Override
public void writeCLayout(PrintStream ps) {
this.writeCOffsetHeader(ps, this.options.comment, this.options.units);
ps.println("\t" + this.enumType + " " + this.name + ";");
}
@Override
public void writeCLayout(PrintStream ps, int arrayLength) {
this.writeCOffsetHeader(ps, this.options.comment, this.options.units);
ps.println("\t" + this.enumType + " " + this.name + "[" + arrayLength + "];");
}
}

View File

@ -0,0 +1,69 @@
package com.rusefi.newparse.layout;
import java.io.PrintStream;
public abstract class Layout {
public int offset = -1;
public int offsetWithinStruct = -1;
public abstract int getSize();
public int getAlignment() {
// Default to size
return this.getSize();
}
public void setOffset(int offset) {
this.offset = offset;
}
public void setOffsetWithinStruct(int offset) {
offsetWithinStruct = offset;
}
@Override
public String toString() {
return "offset = " + offset + " size = " + this.getSize();
}
public final void writeTunerstudioLayout(PrintStream ps) {
writeTunerstudioLayout(ps, new StructNamePrefixer());
}
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {}
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer, int arrayLength) {
throw new IllegalStateException("This type can't be in an array!");
}
protected void writeCOffsetHeader(PrintStream ps, String comment, String units) {
ps.println("\t/**");
if (comment != null) {
comment = comment.replaceAll(";", "");
comment = comment.replaceAll("[+]", "");
comment = comment.replaceAll("\\n", "\n\t * ");
if (comment.length() == 0) {
comment = null;
}
}
if (comment != null) {
comment = comment.replaceAll("\\\\n", "\n\t * ");
ps.println("\t * " + comment);
}
if (units != null && units.length() > 2) {
ps.println("\t" + units.replace("\"", ""));
}
ps.println("\t * offset " + this.offsetWithinStruct);
ps.println("\t */");
}
public void writeCLayout(PrintStream ps) { }
public void writeCLayout(PrintStream ps, int arrayLength) {
throw new IllegalStateException("This type can't be in an array!");
}
}

View File

@ -0,0 +1,73 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.FieldOptions;
import com.rusefi.newparse.parsing.ScalarField;
import com.rusefi.newparse.parsing.Type;
import java.io.PrintStream;
public class ScalarLayout extends Layout {
private String name;
private Type type;
private FieldOptions options;
public ScalarLayout(ScalarField field) {
this.name = field.name;
this.options = field.options;
this.type = field.type;
}
@Override
public int getSize() {
return this.type.size;
}
@Override
public String toString() {
return "Scalar " + type.cType + " " + super.toString();
}
private void printBeforeArrayLength(PrintStream ps, StructNamePrefixer prefixer) {
ps.print(prefixer.get(this.name));
ps.print(" = scalar, ");
ps.print(this.type.tsType);
ps.print(", ");
ps.print(this.offset);
ps.print(", ");
}
private void printAfterArrayLength(PrintStream ps) {
options.printTsFormat(ps);
ps.println();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer, int arrayLength) {
printBeforeArrayLength(ps, prefixer);
ps.print("[");
ps.print(arrayLength);
ps.print("], ");
printAfterArrayLength(ps);
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
printBeforeArrayLength(ps, prefixer);
printAfterArrayLength(ps);
}
@Override
public void writeCLayout(PrintStream ps) {
this.writeCOffsetHeader(ps, this.options.comment, this.options.units);
ps.println("\t" + this.type.cType.replaceAll("^int32_t$", "int") + " " + this.name + ";");
}
@Override
public void writeCLayout(PrintStream ps, int arrayLength) {
this.writeCOffsetHeader(ps, this.options.comment, this.options.units);
ps.println("\t" + this.type.cType.replaceAll("^int32_t$", "int") + " " + this.name + "[" + arrayLength + "];");
}
}

View File

@ -0,0 +1,55 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.StringField;
import com.rusefi.newparse.parsing.UnusedField;
import java.io.PrintStream;
public class StringLayout extends Layout {
private final String name;
private final int size;
public StringLayout(StringField field) {
this.name = field.name;
this.size = field.size;
}
@Override
public int getSize() {
return this.size;
}
@Override
public int getAlignment() {
// char can be single aligned
return 1;
}
@Override
public String toString() {
return "String " + super.toString();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
ps.print(prefixer.get(this.name));
ps.print(" = scalar, ASCII, ");
ps.print(this.offset);
ps.print(", ");
ps.print(size);
ps.println();
}
@Override
public void writeCLayout(PrintStream ps) {
this.writeCOffsetHeader(ps, null, null);
ps.println("\tchar " + this.name + "[" + this.size + "];");
}
@Override
public void writeCLayout(PrintStream ps, int arrayLength) {
this.writeCOffsetHeader(ps, null, null);
ps.println("\tchar " + this.name + "[" + arrayLength + "][" + this.size + "];");
}
}

View File

@ -0,0 +1,204 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.*;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
public class StructLayout extends Layout {
/*private*/public List<Layout> children = new ArrayList<>();
public final String typeName;
private final String name;
private final String comment;
private final Boolean noPrefix;
private final int size;
private static int getAlignedOffset(int offset, int alignment) {
// Align each element to its own size
if ((offset % alignment) != 0) {
return offset + alignment - (offset % alignment);
} else {
return offset;
}
}
int padOffsetWithUnused(int offset, int align) {
int alignedOffset = getAlignedOffset(offset, align);
int needsUnused = alignedOffset - offset;
if (needsUnused > 0) {
UnusedLayout ul = new UnusedLayout(needsUnused);
ul.setOffset(offset);
ul.setOffsetWithinStruct(offset - this.offset);
children.add(ul);
return alignedOffset;
}
return offset;
}
public StructLayout(int offset, String name, Struct parsedStruct) {
setOffset(offset);
this.typeName = parsedStruct.name;
this.name = name;
this.comment = parsedStruct.comment;
this.noPrefix = parsedStruct.noPrefix;
int initialOffest = offset;
for (Field f : parsedStruct.fields) {
if (f instanceof ArrayField) {
ArrayField asf = (ArrayField)f;
// If not a scalar, you must iterate
assert(asf.prototype instanceof ScalarField || asf.iterate);
if (asf.iterate) {
if (asf.prototype instanceof StructField) {
// Struct: special case of a struct array
offset = addItem(offset, new ArrayIterateStructLayout((StructField)asf.prototype, asf.length));
} else {
// array of scalars (or enums)
offset = addItem(offset, new ArrayIterateScalarLayout(asf.prototype, asf.length));
}
} else /* not iterate */ {
// If not a scalar, you must iterate
assert(asf.prototype instanceof ScalarField);
ScalarField prototype = (ScalarField)asf.prototype;
offset = addItem(offset, new ArrayLayout(prototype, asf.length));
}
} else {
offset = addItem(offset, f);
}
}
// Structs are always a multiple of 4 bytes long, pad the end appropriately
offset = padOffsetWithUnused(offset, 4);
size = offset - initialOffest;
}
private int addItem(int offset, Field f) {
if (f instanceof StructField) {
// Special case for structs - we have to compute base offset first
StructField sf = (StructField) f;
return addStruct(offset, sf.struct, sf.name);
}
Layout l = null;
if (f instanceof ScalarField) {
l = new ScalarLayout((ScalarField)f);
} else if (f instanceof EnumField) {
l = new EnumLayout((EnumField)f);
} else if (f instanceof UnusedField) {
l = new UnusedLayout((UnusedField) f);
} else if (f instanceof BitGroup) {
l = new BitGroupLayout((BitGroup) f);
} else if (f instanceof StringField) {
l = new StringLayout((StringField) f);
} else if (f instanceof Union) {
l = new UnionLayout((Union)f);
} else {
throw new RuntimeException("unexpected field type during layout");
}
return addItem(offset, l);
}
private int addItem(int offset, Layout l) {
// Slide the offset up by the required alignment of this element
offset = padOffsetWithUnused(offset, l.getAlignment());
// place the element
l.setOffset(offset);
l.setOffsetWithinStruct(offset - this.offset);
children.add(l);
return offset + l.getSize();
}
private int addStruct(int offset, Struct struct, String name) {
offset = padOffsetWithUnused(offset, 4);
// Recurse and build this new struct
StructLayout sl = new StructLayout(offset, name, struct);
sl.setOffsetWithinStruct(offset - this.offset);
this.children.add(sl);
// Update offset with the struct size - it's guaranteed to be a multiple of 4 bytes
int structSize = sl.getSize();
return offset + structSize;
}
@Override
public int getSize() {
return this.size;
}
@Override
public int getAlignment() {
// All structs should be aligned on a 4 byte boundary
return 4;
}
@Override
public String toString() {
return "Struct " + this.typeName + " " + super.toString();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
if (!this.noPrefix) {
ps.println("; start struct " + this.typeName);
prefixer.push(this.name);
}
// print all children in sequence
this.children.forEach(c -> c.writeTunerstudioLayout(ps, prefixer));
if (!this.noPrefix) {
prefixer.pop();
}
if (!this.noPrefix) {
ps.println("; end struct " + this.typeName);
}
}
@Override
public void writeCLayout(PrintStream ps) {
this.writeCOffsetHeader(ps, null, null);
ps.println("\t" + this.typeName + " " + this.name + ";");
}
@Override
public void writeCLayout(PrintStream ps, int arrayLength) {
this.writeCOffsetHeader(ps, null, null);
ps.println("\t" + this.typeName + " " + this.name + "[" + arrayLength + "];");
}
public void writeCLayoutRoot(PrintStream ps) {
if (this.comment != null) {
ps.println("/**\n * @brief " + this.comment);
ps.println("*/");
}
ps.println("// start of " + this.typeName);
ps.println("struct " + this.typeName + " {");
this.children.forEach(c -> c.writeCLayout(ps));
ps.println("\t/** total size " + getSize() + "*/");
ps.println("};");
ps.println();
}
}

View File

@ -0,0 +1,30 @@
package com.rusefi.newparse.layout;
import java.util.Stack;
import java.util.stream.Collectors;
public class StructNamePrefixer {
private Stack<String> stack = new Stack<>();
public void pop() {
stack.pop();
}
public void push(String name) {
stack.push(name + "_");
}
private String suffix = new String();
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public void resetSuffix() {
this.suffix = new String();
}
String get(String name) {
return stack.stream().collect(Collectors.joining()) + name + suffix;
}
}

View File

@ -0,0 +1,73 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.ArrayField;
import com.rusefi.newparse.parsing.Field;
import com.rusefi.newparse.parsing.ScalarField;
import com.rusefi.newparse.parsing.Union;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
public class UnionLayout extends Layout {
private List<Layout> children = new ArrayList<>();
public UnionLayout(Union u) {
for (Field f : u.fields) {
// For every child, check if it is the largest, and grow if so
if (f instanceof ArrayField) {
ArrayField af = (ArrayField)f;
// we probably don't need union of iterate?
assert(!af.iterate);
ScalarField prototype = (ScalarField)af.prototype;
this.children.add(new ArrayLayout(prototype, af.length));
} else if (f instanceof ScalarField) {
this.children.add(new ScalarLayout((ScalarField)f));
} else {
throw new RuntimeException("Tried to create union with member type " + f.getClass().getSimpleName());
}
}
}
@Override
public void setOffset(int offset) {
super.setOffset(offset);
this.children.stream().forEach(c -> c.setOffset(offset));
}
@Override
public void setOffsetWithinStruct(int offset) {
super.setOffsetWithinStruct(offset);
this.children.stream().forEach(c -> c.setOffsetWithinStruct(offset));
}
@Override
public int getSize() {
return this.children.stream().map(l -> l.getSize()).max(Integer::compare).get();
}
@Override
public int getAlignment() {
// The alignment of the union is the largest alignment required by one of the members
return this.children.stream().map(l -> l.getAlignment()).max(Integer::compare).get();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
// Simply write out all children - no container necessary as fields can overlap in TS
this.children.forEach(c -> c.writeTunerstudioLayout(ps, prefixer));
}
@Override
public void writeCLayout(PrintStream ps) {
this.writeCOffsetHeader(ps, "union size " + this.getSize() + ", " + this.children.size() + " members", null);
// emit an anonymous union that contains all our members
ps.println("\tunion {");
this.children.forEach(c -> c.writeCLayout(ps));
ps.println("\t};");
}
}

View File

@ -0,0 +1,46 @@
package com.rusefi.newparse.layout;
import com.rusefi.newparse.parsing.EnumField;
import com.rusefi.newparse.parsing.Type;
import com.rusefi.newparse.parsing.UnusedField;
import java.io.PrintStream;
import java.util.Random;
public class UnusedLayout extends Layout {
private final int size;
public UnusedLayout(int size) {
this.size = size;
}
public UnusedLayout(UnusedField field) {
this.size = field.size;
}
@Override
public int getSize() {
return this.size;
}
@Override
public int getAlignment() {
return 1;
}
@Override
public String toString() {
return "Unused " + super.toString();
}
@Override
public void writeTunerstudioLayout(PrintStream ps, StructNamePrefixer prefixer) {
ps.println("; unused " + this.size + " bytes at offset " + this.offset);
}
@Override
public void writeCLayout(PrintStream ps) {
this.writeCOffsetHeader(ps, null, null);
ps.println("\tchar unused" + this.offsetWithinStruct + "[" + this.size + "];");
}
}

View File

@ -0,0 +1,11 @@
package com.rusefi.newparse.parsing;
import java.util.List;
public class Union implements Field {
public final List<Field> fields;
public Union(List<Field> fields) {
this.fields = fields;
}
}