Skip to content

Commit

Permalink
Merge pull request #11 from marc-christian-schulze/constants
Browse files Browse the repository at this point in the history
Constants
  • Loading branch information
marc-christian-schulze authored Dec 1, 2024
2 parents f8e7b74 + ccd7fd1 commit 17f0d16
Show file tree
Hide file tree
Showing 16 changed files with 571 additions and 71 deletions.
69 changes: 61 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ Define some structures you would like to read/write in a `*.structs` file under
package com.mycompany.projectx;

struct FileHeader {
uint8_t magic[4];
uint8_t magic[4] const = { 0x50, 0x4b, 0x01, 0x02 };
uint16_t numberSections countof(sections);
FileSection sections[];
}

struct FileSection {
SectionType type;
char name[32];
uint32_t length sizeof(sectionContent);
uint32_t length sizeof(content);
uint8_t content[];
}

Expand Down Expand Up @@ -107,6 +107,7 @@ fileHeader.write(buffer);
* bit fields
* implement Java interfaces
* default values
* constants

## Unsupported
* Unions
Expand All @@ -120,7 +121,7 @@ fileHeader.write(buffer);
The following table shows the built-in data types of Structs4Java. They can be used to compose more advanced types.

| S4J Typename | Java Mapping | Size (bytes) | Subject to Endianess | Description |
| ------------- | ---------------------- | ------------ | ---------------------| -------------------------------------- |
| ------------- | ---------------------- | ------------ | ---------------------|----------------------------------------|
| uint8_t | long | 1 | no | Fixed-size 8bit unsigned integer |
| int8_t | long | 1 | no | Fixed-size 8bit signed integer |
| uint16_t | long | 2 | yes | Fixed-size 16bit unsigned integer |
Expand All @@ -131,11 +132,16 @@ The following table shows the built-in data types of Structs4Java. They can be u
| int64_t | long | 8 | yes | Fixed-size 64bit signed integer |
| float | double | 4 | no | Fixed-size 32bit floating point number |
| double | double | 8 | no | Fixed-size 64bit floating point number |
| char[] | String | variable | no | String of characters (max size 2^31) |
| uint8_t[] | java.nio.ByteBuffer | variable | no | Raw ByteBuffer |
| int8_t[] | java.nio.ByteBuffer | variable | no | Raw ByteBuffer |
| char[] | String | variable | no | String of characters (max size 2^31)<sup>1</sup> |
| uint8_t[] | java.nio.ByteBuffer | variable | no | Raw ByteBuffer<sup>2</sup> |
| int8_t[] | java.nio.ByteBuffer | variable | no | Raw ByteBuffer<sup>2</sup> |

There is no primitive type `char` available. If you need to read a single 1-byte character you can use `char[1]` instead.
<sup>1</sup> There is no primitive type `char` available. If you need to read a single 1-byte character you can use `char[1]` instead.

<sup>2</sup> When a field is mapped to a `java.nio.ByteBuffer`, the underlying buffer stored in the struct is a
slice of the original buffer passed into the `read(java.nio.ByteBuffer)` method, and NOT a copy. Hence, the fields' buffer stays only
valid until you deallocate the original buffer. This is done for performance reasons to avoid reading potential large amounts of
unstructured/binary data until it is explicitly requested.

## Advanced Data Types (Structs & Enums)

Expand Down Expand Up @@ -166,7 +172,7 @@ Variable-sized structures contain at least one array field which dimension is de
```C++
struct BString { // no getSizeOf()
uint32_t length sizeOf(value);
char value[] encoding("UCS-2") null-terminated;
char value[] encoding("UTF-16LE") null-terminated;
}
```
In the given example no explicit dimension of field `value` is provided. Instead the field `length` is marked with the `sizeOf` keyword indicating that it's value will provide the size of the overall array `value` in bytes. You can also use the `countOf` keyword to provide the count of elements an array will contain, e.g.
Expand Down Expand Up @@ -429,6 +435,53 @@ enum SomeEnum : uint8_t {
```
You can NOT define default values for fields within a bitfield, though.

Note that default values are only used during default construction of the structures. A default value is NOT returned if the read operation did not yield any value from the underlying buffer, e.g.
```
struct SomeStruct {
char str[] = "my default";
}
```

```Java
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{});
SomeStruct struct = SomeStruct.read(buffer);

assertEquals("", struct.getStr());
```

```Java
SomeStruct struct = new SomeStruct();

assertEquals("my default", struct.getStr());
```

You can also assign default values to arrays of primitives, e.g.
```
struct SomeStruct {
uint8_t int8[3] = { 1, 0x02, 3 };
uint16_t int16[3] = { 0x45, 2, 3 };
uint32_t int32[3] = { 7, 8, 9 };
uint64_t int64[3] = { 0x20, 0x21, 0x22 };
float f[3] = { 7.534, 1.4, 3.2 };
double d[3] = { 9.75142476, 0.0, 7.7777 }
}
```

## Constants
Constants are fields that have a default value and are marked as `const`, e.g.
```
struct SomeStruct {
uint8_t magic[4] const = { 0x50, 0x4b, 0x01, 0x02 };
}
```
Once a field is marked as `const` only the corresponding getter method will be generated and you won't be able to
override the fields' value. Furthermore, during the read operation of the structure, the value read from the underlying
buffer will be compared with the expected default value. If the values don't match, an `java.io.IOException` will be thrown.

Typical use cases for constants are magic or signature fields within structures.

Constants are NOT supported on structures, enums, bitfields or enum fields (unlike default values).

## Plugin configuration

Below is a full example configuration, including the default values, of the plugin:
Expand Down
6 changes: 3 additions & 3 deletions examples/zip-file-format/src/main/structs/zip.structs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package examples.zip;

struct LocalFileHeader {
uint8_t signature[4];
uint8_t signature[4] const = { 0x50, 0x4b, 0x03, 0x04 };
uint16_t version;
bitfield uint16_t {
uint16_t generalPurposeFlags : 16;
Expand All @@ -25,7 +25,7 @@ struct ExtraFieldRecord {
}

struct CentralDirectoryFileHeader {
uint8_t signature[4];
uint8_t signature[4] const = { 0x50, 0x4b, 0x01, 0x02 };
uint16_t versionMadeBy;
uint16_t versionNeededToExtract;
bitfield uint16_t {
Expand All @@ -50,7 +50,7 @@ struct CentralDirectoryFileHeader {
}

struct EndOfCentralDirectoryRecord {
uint8_t signature[4];
uint8_t signature[4] const = { 0x50, 0x4b, 0x05, 0x06 };
uint16_t diskNumber;
uint16_t diskWhereCentralDirectoryStarts;
uint16_t numberOfCentralDirectoryRecordsOnThisDisk;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public void testWritingZipFile() throws IOException {
// first we create and write the local file header
long positionOfLocalFileHeader = buffer.position();
LocalFileHeader fhFile = new LocalFileHeader();
fhFile.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x03, 0x04 }));
fhFile.setFileName("example.txt");
fhFile.setCompressedSizeInBytes(fileData.length);
fhFile.setUncompressedSizeInBytes(fileData.length);
Expand All @@ -42,7 +41,6 @@ public void testWritingZipFile() throws IOException {
// now we need to create the central directory header
long positionOfFirstCentralDirectoryHeader = buffer.position();
CentralDirectoryFileHeader cdfh = new CentralDirectoryFileHeader();
cdfh.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x01, 0x02 }));
cdfh.setFileName("example.txt");
cdfh.setCrc32OfUncompressedData(crc32);
cdfh.setCompressedSizeInBytes(fileData.length);
Expand All @@ -52,7 +50,6 @@ public void testWritingZipFile() throws IOException {

// finally, we need to write the closing end of central directory header
EndOfCentralDirectoryRecord eocdr = new EndOfCentralDirectoryRecord();
eocdr.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x05, 0x06 }));
eocdr.setOffsetOfStartOfCentralDirectory(positionOfFirstCentralDirectoryHeader);
eocdr.setNumberOfCentralDirectoryRecordsOnThisDisk(1);
eocdr.setTotalNumberOfCentralDirectoryRecords(1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.structs4java.tests

import com.google.inject.Inject
import org.eclipse.xtext.testing.InjectWith
import org.eclipse.xtext.testing.XtextRunner
import org.eclipse.xtext.testing.util.ParseHelper
import org.junit.Test
import org.junit.runner.RunWith
import org.structs4java.structs4JavaDsl.IntegerMember
import org.structs4java.structs4JavaDsl.FloatMember

import static org.junit.Assert.assertEquals

@RunWith(XtextRunner)
@InjectWith(Structs4JavaDslInjectorProvider)
class DefaultValuesParsingTest {

@Inject
ParseHelper<org.structs4java.structs4JavaDsl.StructsFile> parseHelper

@Test
def void structWithValidPrimitivesWithDefaultValues() {
val pkg = parseHelper.parse('''
struct S {
int8_t a = 1;
uint8_t b = 2;
int16_t c = 3;
uint16_t d = 4;
int32_t e = 5;
uint32_t f = 6;
int64_t g = 7;
uint64_t h = 8;
float i = 1.0;
double j = 2.0;
}
''')
val struct = pkg.structs.get(0)

assertEquals(1L, (struct.members.get(0) as IntegerMember).defaultValue)
assertEquals(2L, (struct.members.get(1) as IntegerMember).defaultValue)
assertEquals(3L, (struct.members.get(2) as IntegerMember).defaultValue)
assertEquals(4L, (struct.members.get(3) as IntegerMember).defaultValue)
assertEquals(5L, (struct.members.get(4) as IntegerMember).defaultValue)
assertEquals(6L, (struct.members.get(5) as IntegerMember).defaultValue)
assertEquals(7L, (struct.members.get(6) as IntegerMember).defaultValue)
assertEquals(8L, (struct.members.get(7) as IntegerMember).defaultValue)
assertEquals(1.0f, (struct.members.get(8) as FloatMember).defaultValue, 0.001f)
assertEquals(2.0f, (struct.members.get(9) as FloatMember).defaultValue, 0.001f)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,46 @@ ComplexTypeMember:

IntegerMember:
(comments += SL_COMMENT)*
typename=INTEGER_TYPE name=ID
(array=ArrayDimension)?
typename=INTEGER_TYPE name=ID
(
('sizeof' '(' sizeof=[Member] ')')? &
('sizeof' '(' sizeofThis?='this' ')')? &
('countof' '(' countof=[Member] ')')?
)
('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')?
(constant='const')?
('=' defaultValue=LONG)?
';'
|
(comments += SL_COMMENT)*
typename=INTEGER_TYPE name=ID
array=ArrayDimension
(
('sizeof' '(' sizeof=[Member] ')')? &
('sizeof' '(' sizeofThis?='this' ')')? &
('countof' '(' countof=[Member] ')')?
)
('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')?
(constant='const')?
('=' defaultValues=IntInitializerList)?
';'
;

FloatMember:
(comments += SL_COMMENT)*
typename=FLOAT_TYPE name=ID
(array=ArrayDimension)?
typename=FLOAT_TYPE name=ID
('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')?
(constant='const')?
('=' defaultValue=FLOAT)?
';'
|
(comments += SL_COMMENT)*
typename=FLOAT_TYPE name=ID
array=ArrayDimension
('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')?
(constant='const')?
('=' defaultValues=FloatInitializerList)?
';'
;

StringMember:
Expand All @@ -120,6 +141,7 @@ StringMember:
('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')?
('filler' '(' filler=LONG ')')?
(nullTerminated='null-terminated')?
(constant='const')?
('=' defaultValue=STRING)?
';'
;
Expand All @@ -128,6 +150,15 @@ ArrayDimension:
{ArrayDimension} '[' (dimension=LONG)? ']'
;

IntInitializerList:
{IntInitializerList}
'{' items+=LONG (',' items+=LONG)* '}'
;

FloatInitializerList:
'{' items+=FLOAT (',' items+=FLOAT)* '}'
;

QualifiedName:
ID ('.' ID)*
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,18 @@
*/
package org.structs4java.formatting2

import com.google.inject.Inject
import org.eclipse.xtext.formatting2.AbstractFormatter2
import org.eclipse.xtext.formatting2.IFormattableDocument
import org.structs4java.services.Structs4JavaDslGrammarAccess
import org.structs4java.structs4JavaDsl.EnumDeclaration
import org.structs4java.structs4JavaDsl.Import
import org.structs4java.structs4JavaDsl.Member
import org.structs4java.structs4JavaDsl.StructDeclaration
import org.structs4java.structs4JavaDsl.Item
import org.structs4java.structs4JavaDsl.Structs4JavaDslPackage
import org.eclipse.xtext.formatting2.regionaccess.ILineRegion
import org.eclipse.xtext.EcoreUtil2

class Structs4JavaDslFormatter extends AbstractFormatter2 {

@Inject extension Structs4JavaDslGrammarAccess

def dispatch void format(org.structs4java.structs4JavaDsl.StructsFile structsFile,
extension IFormattableDocument document) {
// TODO: format HiddenRegions around keywords, attributes, cross references, etc.
Expand Down Expand Up @@ -51,7 +46,7 @@ class Structs4JavaDslFormatter extends AbstractFormatter2 {
}

def dispatch void format(Import imp, extension IFormattableDocument document) {
if ((imp.eContainer as org.structs4java.structs4JavaDsl.StructsFile).getImports().last == imp) {
if ((imp.eContainer as org.structs4java.structs4JavaDsl.StructsFile).getImports().lastOrNull == imp) {
imp.regionFor.keyword(";").prepend[noSpace].append[newLines = 2]
} else {
imp.regionFor.keyword(";").prepend[noSpace].append[newLine]
Expand Down
Loading

0 comments on commit 17f0d16

Please sign in to comment.