You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by hu...@apache.org on 2022/11/14 21:18:24 UTC

[plc4x] branch plc4py-codegen created (now 88ecca70ae)

This is an automated email from the ASF dual-hosted git repository.

hutcheb pushed a change to branch plc4py-codegen
in repository https://gitbox.apache.org/repos/asf/plc4x.git


      at 88ecca70ae feat(plc4py/codegen): Copied the Java codegen structure. Need to update flth files now.

This branch includes the following new commits:

     new 88ecca70ae feat(plc4py/codegen): Copied the Java codegen structure. Need to update flth files now.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[plc4x] 01/01: feat(plc4py/codegen): Copied the Java codegen structure. Need to update flth files now.

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hutcheb pushed a commit to branch plc4py-codegen
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 88ecca70aee99eb62722dd89ec7cf1914fd1d984
Author: Ben Hutcheson <be...@gmail.com>
AuthorDate: Mon Nov 14 15:17:27 2022 -0600

    feat(plc4py/codegen): Copied the Java codegen structure. Need to update flth files now.
---
 code-generation/language-python/pom.xml            |  126 ++
 .../language/python/PythonLanguageOutput.java      |  100 ++
 .../python/PythonLanguageTemplateHelper.java       | 1258 ++++++++++++++++++++
 ...x.plugins.codegenerator.language.LanguageOutput |   19 +
 .../python/complex-type-template.python.ftlh       |  977 +++++++++++++++
 .../templates/python/data-io-template.python.ftlh  |  522 ++++++++
 .../templates/python/enum-template.python.ftlh     |  164 +++
 .../src/test/resources/integration-test/pom.xml    |  207 ++++
 .../java/test/readwrite/utils/StaticHelper.java    |   90 ++
 .../src/test/resources/settings.xml                |   53 +
 code-generation/pom.xml                            |    8 +-
 sandbox/plc4py/pom.xml                             |   28 +
 12 files changed, 3551 insertions(+), 1 deletion(-)

diff --git a/code-generation/language-python/pom.xml b/code-generation/language-python/pom.xml
new file mode 100644
index 0000000000..1cc4a29279
--- /dev/null
+++ b/code-generation/language-python/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4x-code-generation</artifactId>
+    <version>0.11.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>plc4x-code-generation-language-python</artifactId>
+
+  <name>Code-Generation: Language: Python</name>
+  <description>Code generation template for generating Python code</description>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-invoker-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>integration-test</id>
+            <goals>
+              <!-- Install the current project artifacts into the maven local repo, so they can be used in the test -->
+              <goal>install</goal>
+              <!-- Execute the maven builds defines in src/test/resources -->
+              <goal>integration-test</goal>
+              <!-- Checks the results of the maven builds -->
+              <goal>verify</goal>
+            </goals>
+            <configuration>
+              <skipInvocation>${skip-code-generation-tests}</skipInvocation>
+              <debug>true</debug>
+              <streamLogsOnFailures>true</streamLogsOnFailures>
+              <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
+              <projectsDirectory>src/test/resources</projectsDirectory>
+              <cloneProjectsTo>${project.build.directory}/integration-tests</cloneProjectsTo>
+              <settingsFile>src/test/resources/settings.xml</settingsFile>
+              <extraArtifacts>
+                <extraArtifact>org.apache.plc4x.plugins:plc4x-code-generation-language-base:${plc4x-code-generation.version}</extraArtifact>
+                <extraArtifact>org.apache.plc4x.plugins:plc4x-maven-plugin:${plc4x-code-generation.version}</extraArtifact>
+                <extraArtifact>org.apache.plc4x.plugins:plc4x-code-generation-protocol-base:${plc4x-code-generation.version}</extraArtifact>
+                <extraArtifact>org.apache.plc4x.plugins:plc4x-code-generation-types-base:${plc4x-code-generation.version}</extraArtifact>
+              </extraArtifacts>
+              <pomIncludes>
+                <pomInclude>*/pom.xml</pomInclude>
+              </pomIncludes>
+              <!-- The goals we will be executing in the test-projects -->
+              <goals>
+                <goal>test</goal>
+              </goals>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <!-- We are using the Freemarker module to generate Java code -->
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-language-base-freemarker</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-base-mspec</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x.plugins</groupId>
+      <artifactId>plc4x-code-generation-types-base</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.freemarker</groupId>
+      <artifactId>freemarker</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-text</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.googlejavaformat</groupId>
+      <artifactId>google-java-format</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-test</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-test</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+      <classifier>tests</classifier>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageOutput.java b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageOutput.java
new file mode 100644
index 0000000000..bb48c9023a
--- /dev/null
+++ b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageOutput.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.language.python;
+
+import com.google.googlejavaformat.java.Formatter;
+import com.google.googlejavaformat.java.FormatterException;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import org.apache.commons.io.FileUtils;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.FreemarkerLanguageOutput;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.FreemarkerLanguageTemplateHelper;
+import org.apache.plc4x.plugins.codegenerator.types.definitions.TypeDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class PythonLanguageOutput extends FreemarkerLanguageOutput {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(PythonLanguageOutput.class);
+
+    private final Formatter formatter = new Formatter();
+
+    @Override
+    public String getName() {
+        return "python";
+    }
+
+    @Override
+    public Set<String> supportedOptions() {
+        return Collections.singleton("package");
+    }
+
+    @Override
+    public List<String> supportedOutputFlavors() {
+        return Arrays.asList("read-write", "read-only", "passive");
+    }
+
+    @Override
+    protected List<Template> getSpecTemplates(Configuration freemarkerConfiguration) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    protected List<Template> getComplexTypeTemplates(Configuration freemarkerConfiguration) throws IOException {
+        return List.of(freemarkerConfiguration.getTemplate("templates/python/complex-type-template.python.ftlh"));
+    }
+
+    @Override
+    protected List<Template> getEnumTypeTemplates(Configuration freemarkerConfiguration) throws IOException {
+        return Collections.singletonList(
+            freemarkerConfiguration.getTemplate("templates/python/enum-template.python.ftlh"));
+    }
+
+    @Override
+    protected List<Template> getDataIoTemplates(Configuration freemarkerConfiguration) throws IOException {
+        return Collections.singletonList(
+            freemarkerConfiguration.getTemplate("templates/python/data-io-template.python.ftlh"));
+    }
+
+    @Override
+    protected FreemarkerLanguageTemplateHelper getHelper(TypeDefinition thisType, String protocolName, String flavorName, Map<String, TypeDefinition> types,
+                                                         Map<String, String> options) {
+        return new PythonLanguageTemplateHelper(thisType, protocolName, flavorName, types, options);
+    }
+
+    @Override
+    protected void postProcessTemplateOutput(File outputFile) {
+        try {
+            FileUtils.writeStringToFile(
+                outputFile,
+                formatter.formatSourceAndFixImports(
+                    FileUtils.readFileToString(outputFile, StandardCharsets.UTF_8)
+                ),
+                StandardCharsets.UTF_8
+            );
+        } catch (IOException | FormatterException e) {
+            LOGGER.error("Error formatting {}", outputFile, e);
+        }
+    }
+}
diff --git a/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java
new file mode 100644
index 0000000000..b5ec041b41
--- /dev/null
+++ b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java
@@ -0,0 +1,1258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.language.python;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.text.WordUtils;
+import org.apache.plc4x.plugins.codegenerator.language.mspec.model.terms.DefaultStringLiteral;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.BaseFreemarkerLanguageTemplateHelper;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.FreemarkerException;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer;
+import org.apache.plc4x.plugins.codegenerator.types.definitions.*;
+import org.apache.plc4x.plugins.codegenerator.types.fields.*;
+import org.apache.plc4x.plugins.codegenerator.types.references.*;
+import org.apache.plc4x.plugins.codegenerator.types.terms.*;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.*;
+import java.util.function.Function;
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class PythonLanguageTemplateHelper extends BaseFreemarkerLanguageTemplateHelper {
+
+    private final Map<String, String> options;
+
+    public PythonLanguageTemplateHelper(TypeDefinition thisType, String protocolName, String flavorName, Map<String, TypeDefinition> types,
+                                      Map<String, String> options) {
+        super(thisType, protocolName, flavorName, types);
+        this.options = options;
+    }
+
+    public String packageName() {
+        return packageName(protocolName, "python", flavorName);
+    }
+
+    public String packageName(String protocolName, String languageName, String languageFlavorName) {
+        return Optional.ofNullable(options.get("package")).orElseGet(() ->
+            "org.apache.plc4x." + String.join("", languageName.split("-")) + "." +
+                String.join("", protocolName.split("-")) + "." +
+                String.join("", languageFlavorName.split("-")));
+    }
+
+    @Override
+    public String getLanguageTypeNameForField(Field field) {
+        // If the referenced type is a DataIo type, the value is of type PlcValue.
+        if (field.isPropertyField()) {
+            PropertyField propertyField = field.asPropertyField().orElseThrow(IllegalStateException::new);
+            if (propertyField.getType().isComplexTypeReference()) {
+                ComplexTypeReference complexTypeReference = propertyField.getType().asComplexTypeReference().orElseThrow(IllegalStateException::new);
+                final TypeDefinition typeDefinition = getTypeDefinitions().get(complexTypeReference.getName());
+                if (typeDefinition instanceof DataIoTypeDefinition) {
+                    return "PlcValue";
+                }
+            }
+        }
+        return getLanguageTypeNameForTypeReference(((TypedField) field).getType(), !field.isOptionalField());
+    }
+
+    public String getNonPrimitiveLanguageTypeNameForField(TypedField field) {
+        return getLanguageTypeNameForTypeReference(field.getType(), false);
+    }
+
+    public String getLanguageTypeNameForSpecType(TypeReference typeReference) {
+        return getLanguageTypeNameForTypeReference(typeReference, true);
+    }
+
+    @Override
+    public String getLanguageTypeNameForTypeReference(TypeReference typeReference) {
+        return getLanguageTypeNameForTypeReference(typeReference, false);
+    }
+
+    public String getLanguageTypeNameForTypeReference(TypeReference typeReference, boolean allowPrimitive) {
+        Objects.requireNonNull(typeReference);
+        if (typeReference instanceof ArrayTypeReference) {
+            final ArrayTypeReference arrayTypeReference = (ArrayTypeReference) typeReference;
+            if (arrayTypeReference.getElementTypeReference().isByteBased()) {
+                return getLanguageTypeNameForTypeReference(arrayTypeReference.getElementTypeReference(), allowPrimitive) + "[]";
+            } else {
+                return "List<" + getLanguageTypeNameForTypeReference(arrayTypeReference.getElementTypeReference(), false) + ">";
+            }
+        }
+        // DataIo data-types always have properties of type PlcValue
+        if (typeReference.isDataIoTypeReference()) {
+            return "PlcValue";
+        }
+        if (typeReference.isNonSimpleTypeReference()) {
+            return typeReference.asNonSimpleTypeReference().orElseThrow().getName();
+        }
+        SimpleTypeReference simpleTypeReference = (SimpleTypeReference) typeReference;
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return allowPrimitive ? boolean.class.getSimpleName() : Boolean.class.getSimpleName();
+            case BYTE:
+                return allowPrimitive ? byte.class.getSimpleName() : Byte.class.getSimpleName();
+            case UINT:
+                IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 4) {
+                    return allowPrimitive ? byte.class.getSimpleName() : Byte.class.getSimpleName();
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 8) {
+                    return allowPrimitive ? short.class.getSimpleName() : Short.class.getSimpleName();
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                    return allowPrimitive ? int.class.getSimpleName() : Integer.class.getSimpleName();
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                    return allowPrimitive ? long.class.getSimpleName() : Long.class.getSimpleName();
+                }
+                return BigInteger.class.getSimpleName();
+            case INT:
+                IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (integerTypeReference.getSizeInBits() <= 8) {
+                    return allowPrimitive ? byte.class.getSimpleName() : Byte.class.getSimpleName();
+                }
+                if (integerTypeReference.getSizeInBits() <= 16) {
+                    return allowPrimitive ? short.class.getSimpleName() : Short.class.getSimpleName();
+                }
+                if (integerTypeReference.getSizeInBits() <= 32) {
+                    return allowPrimitive ? int.class.getSimpleName() : Integer.class.getSimpleName();
+                }
+                if (integerTypeReference.getSizeInBits() <= 64) {
+                    return allowPrimitive ? long.class.getSimpleName() : Long.class.getSimpleName();
+                }
+                return BigInteger.class.getSimpleName();
+            case FLOAT:
+            case UFLOAT:
+                FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                int sizeInBits = floatTypeReference.getSizeInBits();
+                if (sizeInBits <= 32) {
+                    return allowPrimitive ? float.class.getSimpleName() : Float.class.getSimpleName();
+                }
+                if (sizeInBits <= 64) {
+                    return allowPrimitive ? double.class.getSimpleName() : Double.class.getSimpleName();
+                }
+                return BigDecimal.class.getSimpleName();
+            case STRING:
+            case VSTRING:
+                return String.class.getSimpleName();
+            case TIME:
+                return LocalTime.class.getSimpleName();
+            case DATE:
+                return LocalDate.class.getSimpleName();
+            case DATETIME:
+                return LocalDateTime.class.getSimpleName();
+
+        }
+        throw new RuntimeException("Unsupported simple type");
+    }
+
+    public String getPlcValueTypeForTypeReference(TypeReference typeReference) {
+        if (!(typeReference instanceof SimpleTypeReference)) {
+            return "PlcStruct";
+        }
+        SimpleTypeReference simpleTypeReference = (SimpleTypeReference) typeReference;
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return "PlcBOOL";
+            case BYTE:
+                return "PlcSINT";
+            case UINT:
+                IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 4) {
+                    return "PlcUSINT";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 8) {
+                    return "PlcUINT";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                    return "PlcUDINT";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                    return "PlcULINT";
+                }
+            case INT:
+                IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (integerTypeReference.getSizeInBits() <= 8) {
+                    return "PlcSINT";
+                }
+                if (integerTypeReference.getSizeInBits() <= 16) {
+                    return "PlcINT";
+                }
+                if (integerTypeReference.getSizeInBits() <= 32) {
+                    return "PlcDINT";
+                }
+                if (integerTypeReference.getSizeInBits() <= 64) {
+                    return "PlcLINT";
+                }
+
+            case FLOAT:
+            case UFLOAT:
+                FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                int sizeInBits = floatTypeReference.getSizeInBits();
+                if (sizeInBits <= 32) {
+                    return "PlcREAL";
+                }
+                if (sizeInBits <= 64) {
+                    return "PlcLREAL";
+                }
+            case STRING:
+            case VSTRING:
+                return "PlcSTRING";
+            case TIME:
+            case DATE:
+            case DATETIME:
+                return "PlcTIME";
+        }
+        throw new RuntimeException("Unsupported simple type");
+    }
+
+    @Override
+    public String getNullValueForTypeReference(TypeReference typeReference) {
+        if (typeReference instanceof SimpleTypeReference) {
+            SimpleTypeReference simpleTypeReference = (SimpleTypeReference) typeReference;
+            switch (simpleTypeReference.getBaseType()) {
+                case BIT:
+                    return "false";
+                case BYTE:
+                    return "0";
+                case UINT:
+                    IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                    if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                        return "0";
+                    }
+                    if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                        return "0l";
+                    }
+                    return "null";
+                case INT:
+                    IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                    if (integerTypeReference.getSizeInBits() <= 32) {
+                        return "0";
+                    }
+                    if (integerTypeReference.getSizeInBits() <= 64) {
+                        return "0l";
+                    }
+                    return "null";
+                case FLOAT:
+                    FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                    int sizeInBits = floatTypeReference.getSizeInBits();
+                    if (sizeInBits <= 32) {
+                        return "0.0f";
+                    }
+                    if (sizeInBits <= 64) {
+                        return "0.0";
+                    }
+                    return "null";
+                case STRING:
+                case VSTRING:
+                    return "null";
+            }
+            throw new FreemarkerException("Unmapped base-type" + simpleTypeReference.getBaseType());
+        } else {
+            return "null";
+        }
+    }
+
+    public int getNumBits(SimpleTypeReference simpleTypeReference) {
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return 1;
+            case BYTE:
+                return 8;
+            case UINT:
+            case INT:
+                IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                return integerTypeReference.getSizeInBits();
+            case FLOAT:
+                FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                return floatTypeReference.getSizeInBits();
+            case STRING:
+                StringTypeReference stringTypeReference = (StringTypeReference) simpleTypeReference;
+                return stringTypeReference.getSizeInBits();
+            case VSTRING:
+                throw new IllegalArgumentException("getSizeInBits doesn't work for 'vstring' fields");
+            default:
+                return 0;
+        }
+    }
+
+    @Deprecated
+    @Override
+    public String getReadBufferReadMethodCall(SimpleTypeReference simpleTypeReference, String valueString, TypedField field) {
+        return "/*TODO: migrate me*/" + getReadBufferReadMethodCall("", simpleTypeReference, valueString, field);
+    }
+
+    @Deprecated
+    public String getReadBufferReadMethodCall(String logicalName, SimpleTypeReference simpleTypeReference, String valueString, TypedField field) {
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                String bitType = "Bit";
+                return "/*TODO: migrate me*/" + "readBuffer.read" + bitType + "(\"" + logicalName + "\")";
+            case BYTE:
+                String byteType = "Byte";
+                return "/*TODO: migrate me*/" + "readBuffer.read" + byteType + "(\"" + logicalName + "\")";
+            case UINT:
+                String unsignedIntegerType;
+                IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 4) {
+                    unsignedIntegerType = "UnsignedByte";
+                } else if (unsignedIntegerTypeReference.getSizeInBits() <= 8) {
+                    unsignedIntegerType = "UnsignedShort";
+                } else if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                    unsignedIntegerType = "UnsignedInt";
+                } else if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                    unsignedIntegerType = "UnsignedLong";
+                } else {
+                    unsignedIntegerType = "UnsignedBigInteger";
+                }
+                return "/*TODO: migrate me*/" + "readBuffer.read" + unsignedIntegerType + "(\"" + logicalName + "\", " + simpleTypeReference.getSizeInBits() + ")";
+            case INT:
+                String integerType;
+                if (simpleTypeReference.getSizeInBits() <= 8) {
+                    integerType = "SignedByte";
+                } else if (simpleTypeReference.getSizeInBits() <= 16) {
+                    integerType = "Short";
+                } else if (simpleTypeReference.getSizeInBits() <= 32) {
+                    integerType = "Int";
+                } else if (simpleTypeReference.getSizeInBits() <= 64) {
+                    integerType = "Long";
+                } else {
+                    integerType = "BigInteger";
+                }
+                return "/*TODO: migrate me*/" + "readBuffer.read" + integerType + "(\"" + logicalName + "\", " + simpleTypeReference.getSizeInBits() + ")";
+            case FLOAT:
+                String floatType = (simpleTypeReference.getSizeInBits() <= 32) ? "Float" : "Double";
+                return "/*TODO: migrate me*/" + "readBuffer.read" + floatType + "(\"" + logicalName + "\", " + simpleTypeReference.getSizeInBits() + ")";
+            case STRING:
+            case VSTRING:
+                String stringType = "String";
+                final Term encodingTerm = field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"));
+                if (!(encodingTerm instanceof StringLiteral)) {
+                    throw new RuntimeException("Encoding must be a quoted string value");
+                }
+                String encoding = ((StringLiteral) encodingTerm).getValue();
+                String length = Integer.toString(simpleTypeReference.getSizeInBits());
+                if (simpleTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.VSTRING) {
+                    VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference;
+                    length = toParseExpression(field, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), null);
+                }
+                return "/*TODO: migrate me*/" + "readBuffer.read" + stringType + "(\"" + logicalName + "\", " + length + ", \"" +
+                    encoding + "\")";
+        }
+        return "/*TODO: migrate me*/" + "";
+    }
+
+    public String getDataReaderCall(TypeReference typeReference) {
+        return getDataReaderCall(typeReference, "enumForValue");
+    }
+
+    public String getDataReaderCall(TypeReference typeReference, String resolverMethod) {
+        if (typeReference.isEnumTypeReference()) {
+            final String languageTypeName = getLanguageTypeNameForTypeReference(typeReference);
+            final SimpleTypeReference enumBaseTypeReference = getEnumBaseTypeReference(typeReference);
+            return "new DataReaderEnumDefault<>(" + languageTypeName + "::" + resolverMethod + ", " + getDataReaderCall(enumBaseTypeReference) + ")";
+        } else if (typeReference.isArrayTypeReference()) {
+            final ArrayTypeReference arrayTypeReference = typeReference.asArrayTypeReference().orElseThrow();
+            return getDataReaderCall(arrayTypeReference.getElementTypeReference(), resolverMethod);
+        } else if (typeReference.isSimpleTypeReference()) {
+            SimpleTypeReference simpleTypeReference = typeReference.asSimpleTypeReference().orElseThrow(IllegalStateException::new);
+            return getDataReaderCall(simpleTypeReference);
+        } else if (typeReference.isComplexTypeReference()) {
+            StringBuilder paramsString = new StringBuilder();
+            ComplexTypeReference complexTypeReference = typeReference.asComplexTypeReference().orElseThrow(IllegalStateException::new);
+            ComplexTypeDefinition typeDefinition = complexTypeReference.getTypeDefinition();
+            String parserCallString = getLanguageTypeNameForTypeReference(typeReference);
+            // In case of DataIo we actually need to use the type name and not what above returns.
+            // (In this case the mspec type name and the result type name differ)
+            if (typeReference.isDataIoTypeReference()) {
+                parserCallString = typeReference.asDataIoTypeReference().orElseThrow().getName();
+            }
+            if (typeDefinition.isDiscriminatedChildTypeDefinition()) {
+                parserCallString = "(" + getLanguageTypeNameForTypeReference(typeReference) + ") " + typeDefinition.getParentType().orElseThrow().getName();
+            }
+            List<Term> paramTerms = complexTypeReference.getParams().orElse(Collections.emptyList());
+            for (int i = 0; i < paramTerms.size(); i++) {
+                Term paramTerm = paramTerms.get(i);
+                final TypeReference argumentType = getArgumentType(complexTypeReference, i);
+                paramsString
+                    .append(", (")
+                    .append(getLanguageTypeNameForTypeReference(argumentType, true))
+                    .append(") (")
+                    .append(toParseExpression(null, argumentType, paramTerm, null))
+                    .append(")");
+            }
+            return "new DataReaderComplexDefault<>(() -> " + parserCallString + ".staticParse(readBuffer" + paramsString + "), readBuffer)";
+        } else {
+            throw new IllegalStateException("What is this type? " + typeReference);
+        }
+    }
+
+    public String getDataReaderCall(SimpleTypeReference simpleTypeReference) {
+        final int sizeInBits = simpleTypeReference.getSizeInBits();
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return "readBoolean(readBuffer)";
+            case BYTE:
+                return "readByte(readBuffer, " + sizeInBits + ")";
+            case UINT:
+                if (sizeInBits <= 4) return "readUnsignedByte(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 8) return "readUnsignedShort(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 16) return "readUnsignedInt(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 32) return "readUnsignedLong(readBuffer, " + sizeInBits + ")";
+                return "readUnsignedBigInteger(readBuffer, " + sizeInBits + ")";
+            case INT:
+                if (sizeInBits <= 8) return "readSignedByte(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 16) return "readSignedShort(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 32) return "readSignedInt(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 64) return "readSignedLong(readBuffer, " + sizeInBits + ")";
+                return "readSignedBigInteger(readBuffer, " + sizeInBits + ")";
+            case FLOAT:
+                if (sizeInBits <= 32) return "readFloat(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 64) return "readDouble(readBuffer, " + sizeInBits + ")";
+                return "readBigDecimal(readBuffer, " + sizeInBits + ")";
+            case STRING:
+                return "readString(readBuffer, " + sizeInBits + ")";
+            case VSTRING:
+                VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference;
+                return "readString(readBuffer, " + toParseExpression(null, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), null) + ")";
+            case TIME:
+                return "readTime(readBuffer)";
+            case DATE:
+                return "readDate(readBuffer)";
+            case DATETIME:
+                return "readDateTime(readBuffer)";
+            default:
+                throw new UnsupportedOperationException("Unsupported type " + simpleTypeReference.getBaseType());
+        }
+    }
+
+    public String getDataWriterCall(TypeReference typeReference, String fieldName) {
+        if (typeReference.isSimpleTypeReference()) {
+            SimpleTypeReference simpleTypeReference = typeReference.asSimpleTypeReference().orElseThrow(IllegalStateException::new);
+            return getDataWriterCall(simpleTypeReference);
+        } else if (typeReference.isArrayTypeReference()) {
+            final ArrayTypeReference arrayTypeReference = typeReference.asArrayTypeReference().orElseThrow();
+            return getDataWriterCall(arrayTypeReference.getElementTypeReference(), fieldName);
+        } else if (typeReference.isComplexTypeReference()) {
+            return "new DataWriterComplexDefault<>(writeBuffer)";
+        } else {
+            throw new IllegalStateException("What is this type? " + typeReference);
+        }
+    }
+
+    public String getEnumDataWriterCall(EnumTypeReference typeReference, String fieldName, String attributeName) {
+        if (!typeReference.isEnumTypeReference()) {
+            throw new IllegalArgumentException("this method should only be called for enum types");
+        }
+        final String languageTypeName = getLanguageTypeNameForTypeReference(typeReference);
+        SimpleTypeReference outputTypeReference;
+        if ("value".equals(attributeName)) {
+            outputTypeReference = getEnumBaseTypeReference(typeReference);
+        } else {
+            outputTypeReference = getEnumFieldSimpleTypeReference(typeReference.asNonSimpleTypeReference().orElseThrow(), attributeName);
+        }
+        return "new DataWriterEnumDefault<>(" + languageTypeName + "::get" + StringUtils.capitalize(attributeName) + ", " + languageTypeName + "::name, " + getDataWriterCall(outputTypeReference, fieldName) + ")";
+    }
+
+    public String getDataWriterCall(SimpleTypeReference simpleTypeReference) {
+        final int sizeInBits = simpleTypeReference.getSizeInBits();
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return "writeBoolean(writeBuffer)";
+            case BYTE:
+                return "writeByte(writeBuffer, " + sizeInBits + ")";
+            case UINT:
+                if (sizeInBits <= 4) return "writeUnsignedByte(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 8) return "writeUnsignedShort(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 16) return "writeUnsignedInt(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 32) return "writeUnsignedLong(writeBuffer, " + sizeInBits + ")";
+                return "writeUnsignedBigInteger(writeBuffer, " + sizeInBits + ")";
+            case INT:
+                if (sizeInBits <= 8) return "writeSignedByte(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 16) return "writeSignedShort(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 32) return "writeSignedInt(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 64) return "writeSignedLong(writeBuffer, " + sizeInBits + ")";
+                return "writeSignedBigInteger(writeBuffer, " + sizeInBits + ")";
+            case FLOAT:
+                if (sizeInBits <= 32) return "writeFloat(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 64) return "writeDouble(writeBuffer, " + sizeInBits + ")";
+                return "writeBigDecimal(writeBuffer, " + sizeInBits + ")";
+            case STRING:
+                return "writeString(writeBuffer, " + sizeInBits + ")";
+            case VSTRING:
+                VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference;
+                return "writeString(writeBuffer, " + toParseExpression(null, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), null) + ")";
+            case TIME:
+                return "writeTime(writeBuffer)";
+            case DATE:
+                return "writeDate(writeBuffer)";
+            case DATETIME:
+                return "writeDateTime(writeBuffer)";
+            default:
+                throw new UnsupportedOperationException("Unsupported type " + simpleTypeReference.getBaseType());
+        }
+    }
+
+    @Deprecated
+    @Override
+    public String getWriteBufferWriteMethodCall(SimpleTypeReference simpleTypeReference, String fieldName, TypedField field) {
+        return "/*TODO: migrate me*/" + getWriteBufferWriteMethodCall("", simpleTypeReference, fieldName, field);
+    }
+
+    @Deprecated
+    public String getWriteBufferWriteMethodCall(String logicalName, SimpleTypeReference simpleTypeReference, String fieldName, TypedField field, String... writerArgs) {
+        String writerArgsString = "";
+        if (writerArgs.length > 0) {
+            writerArgsString += ", " + StringUtils.join(writerArgs, ", ");
+        }
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return "/*TODO: migrate me*/" + "writeBuffer.writeBit(\"" + logicalName + "\", (boolean) " + fieldName + "" + writerArgsString + ")";
+            case BYTE:
+                ByteTypeReference byteTypeReference = (ByteTypeReference) simpleTypeReference;
+                return "/*TODO: migrate me*/" + "writeBuffer.writeByte(\"" + logicalName + "\", ((Number) " + fieldName + ").byteValue()" + writerArgsString + ")";
+            case UINT:
+                IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 4) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedByte(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").byteValue()" + writerArgsString + ")";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 8) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedShort(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").shortValue()" + writerArgsString + ")";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedInt(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").intValue()" + writerArgsString + ")";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedLong(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").longValue()" + writerArgsString + ")";
+                }
+                return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedBigInteger(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", (BigInteger) " + fieldName + "" + writerArgsString + ")";
+            case INT:
+                IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (integerTypeReference.getSizeInBits() <= 8) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeSignedByte(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").byteValue()" + writerArgsString + ")";
+                }
+                if (integerTypeReference.getSizeInBits() <= 16) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeShort(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").shortValue()" + writerArgsString + ")";
+                }
+                if (integerTypeReference.getSizeInBits() <= 32) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeInt(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").intValue()" + writerArgsString + ")";
+                }
+                if (integerTypeReference.getSizeInBits() <= 64) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeLong(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").longValue()" + writerArgsString + ")";
+                }
+                return "/*TODO: migrate me*/" + "writeBuffer.writeBigInteger(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", BigInteger.valueOf( " + fieldName + ")" + writerArgsString + ")";
+            case FLOAT:
+            case UFLOAT:
+                FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                if (floatTypeReference.getSizeInBits() <= 32) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeFloat(\"" + logicalName + "\", " + floatTypeReference.getSizeInBits() + "," + fieldName + "" + writerArgsString + ")";
+                } else if (floatTypeReference.getSizeInBits() <= 64) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeDouble(\"" + logicalName + "\", " + floatTypeReference.getSizeInBits() + "," + fieldName + "" + writerArgsString + ")";
+                } else {
+                    throw new RuntimeException("Unsupported float type");
+                }
+            case STRING:
+            case VSTRING:
+                final Term encodingTerm = field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"));
+                if (!(encodingTerm instanceof StringLiteral)) {
+                    throw new RuntimeException("Encoding must be a quoted string value");
+                }
+                String encoding = ((StringLiteral) encodingTerm).getValue();
+                String length = Integer.toString(simpleTypeReference.getSizeInBits());
+                if (simpleTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.VSTRING) {
+                    VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference;
+                    length = toSerializationExpression(field, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), thisType.getParserArguments().orElse(Collections.emptyList()));
+                }
+                return "/*TODO: migrate me*/" + "writeBuffer.writeString(\"" + logicalName + "\", " + length + ", \"" +
+                    encoding + "\", (String) " + fieldName + "" + writerArgsString + ")";
+        }
+        throw new FreemarkerException("Unmapped basetype" + simpleTypeReference.getBaseType());
+    }
+
+    public String getReservedValue(ReservedField reservedField) {
+        final String languageTypeName = getLanguageTypeNameForTypeReference(reservedField.getType(), true);
+        if ("BigInteger".equals(languageTypeName)) {
+            return "BigInteger.valueOf(" + reservedField.getReferenceValue() + ")";
+        } else {
+            return "(" + languageTypeName + ") " + reservedField.getReferenceValue();
+        }
+    }
+
+    /**
+     * @param field           this generally only is needed in order to access field attributes such as encoding etc.
+     * @param resultType      the type the resulting expression should have
+     * @param term            the term representing the expression
+     * @param parserArguments any parser arguments, which could be referenced in expressions (Needed for getting the type)
+     * @return Java code which does the things defined in 'term'
+     */
+    public String toParseExpression(Field field, TypeReference resultType, Term term, List<Argument> parserArguments) {
+        Tracer tracer = Tracer.start("toParseExpression");
+        return tracer + toExpression(field, resultType, term, variableLiteral -> tracer.dive("variableExpressionGenerator") + toVariableParseExpression(field, resultType, variableLiteral, parserArguments));
+    }
+
+    /**
+     * @param field               this generally only is needed in order to access field attributes such as encoding etc.
+     * @param resultType          the type the resulting expression should have
+     * @param term                the term representing the expression
+     * @param serializerArguments any serializer arguments, which could be referenced in expressions (Needed for getting the type)
+     * @return Java code which does the things defined in 'term'
+     */
+    public String toSerializationExpression(Field field, TypeReference resultType, Term term, List<Argument> serializerArguments) {
+        Tracer tracer = Tracer.start("toSerializationExpression");
+        return tracer + toExpression(field, resultType, term, variableLiteral -> tracer.dive("variableExpressionGenerator") + toVariableSerializationExpression(field, resultType, variableLiteral, serializerArguments));
+    }
+
+    private String toExpression(Field field, TypeReference resultType, Term term, Function<VariableLiteral, String> variableExpressionGenerator) {
+        Tracer tracer = Tracer.start("toExpression");
+        if (term == null) {
+            return tracer + "";
+        }
+        if (term instanceof Literal) {
+            return toLiteralTermExpression(field, resultType, (Literal) term, variableExpressionGenerator, tracer);
+        } else if (term instanceof UnaryTerm) {
+            return toUnaryTermExpression(field, resultType, (UnaryTerm) term, variableExpressionGenerator, tracer);
+        } else if (term instanceof BinaryTerm) {
+            return toBinaryTermExpression(field, resultType, (BinaryTerm) term, variableExpressionGenerator, tracer);
+        } else if (term instanceof TernaryTerm) {
+            return toTernaryTermExpression(field, resultType, (TernaryTerm) term, variableExpressionGenerator, tracer);
+        } else {
+            throw new RuntimeException("Unsupported Term type " + term.getClass().getName() + ". Actual type " + resultType);
+        }
+    }
+
+    private String toLiteralTermExpression(Field field, TypeReference resultType, Literal literal, Function<VariableLiteral, String> variableExpressionGenerator, Tracer tracer) {
+        tracer = tracer.dive("literal term instanceOf");
+        if (literal instanceof NullLiteral) {
+            tracer = tracer.dive("null literal instanceOf");
+            return tracer + "null";
+        } else if (literal instanceof BooleanLiteral) {
+            tracer = tracer.dive("boolean literal instanceOf");
+            return tracer + Boolean.toString(((BooleanLiteral) literal).getValue());
+        } else if (literal instanceof NumericLiteral) {
+            tracer = tracer.dive("numeric literal instanceOf");
+            final String numberString = ((NumericLiteral) literal).getNumber().toString();
+            if (resultType.isIntegerTypeReference()) {
+                final IntegerTypeReference integerTypeReference = resultType.asIntegerTypeReference().orElseThrow(RuntimeException::new);
+                if (integerTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.UINT && integerTypeReference.getSizeInBits() >= 32) {
+                    tracer = tracer.dive("uint >= 32bit");
+                    return tracer + numberString + "L";
+                } else if (integerTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.INT && integerTypeReference.getSizeInBits() > 32) {
+                    tracer = tracer.dive("int > 32bit");
+                    return tracer + numberString + "L";
+                }
+            } else if (resultType.isFloatTypeReference()) {
+                final FloatTypeReference floatTypeReference = resultType.asFloatTypeReference().orElseThrow(RuntimeException::new);
+                if (floatTypeReference.getSizeInBits() <= 32) {
+                    tracer = tracer.dive("float < 32bit");
+                    return tracer + numberString + "F";
+                }
+            }
+            return tracer + numberString;
+        } else if (literal instanceof HexadecimalLiteral) {
+            tracer = tracer.dive("hexadecimal literal instanceOf");
+            final String hexString = ((HexadecimalLiteral) literal).getHexString();
+            if (resultType.isIntegerTypeReference()) {
+                final IntegerTypeReference integerTypeReference = resultType.asIntegerTypeReference().orElseThrow(RuntimeException::new);
+                if (integerTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.UINT && integerTypeReference.getSizeInBits() >= 32) {
+                    tracer = tracer.dive("uint >= 32bit");
+                    return tracer + hexString + "L";
+                } else if (integerTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.INT && integerTypeReference.getSizeInBits() > 32) {
+                    tracer = tracer.dive("int > 32bit");
+                    return tracer + hexString + "L";
+                }
+            }
+            return tracer + hexString;
+        } else if (literal instanceof StringLiteral) {
+            tracer = tracer.dive("string literal instanceOf");
+            return tracer + "\"" + ((StringLiteral) literal).getValue() + "\"";
+        } else if (literal instanceof VariableLiteral) {
+            tracer = tracer.dive("variable literal instanceOf");
+            VariableLiteral variableLiteral = (VariableLiteral) literal;
+            if ("curPos".equals(((VariableLiteral) literal).getName())) {
+                return "(positionAware.getPos() - startPos)";
+            }
+            // If this literal references an Enum type, then we have to output it differently.
+            if (getTypeDefinitions().get(variableLiteral.getName()) instanceof EnumTypeDefinition) {
+                tracer = tracer.dive("enum definition instanceOf");
+                VariableLiteral enumDefinitionChild = variableLiteral.getChild()
+                    .orElseThrow(() -> new RuntimeException("enum definitions should have childs"));
+                return tracer + variableLiteral.getName() + "." + enumDefinitionChild.getName() +
+                    enumDefinitionChild.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+            } else {
+                return tracer + variableExpressionGenerator.apply(variableLiteral);
+            }
+        } else {
+            throw new RuntimeException("Unsupported Literal type " + literal.getClass().getName());
+        }
+    }
+
+    private String toUnaryTermExpression(Field field, TypeReference resultType, UnaryTerm unaryTerm, Function<VariableLiteral, String> variableExpressionGenerator, Tracer tracer) {
+        tracer = tracer.dive("unary term instanceOf");
+        Term a = unaryTerm.getA();
+        switch (unaryTerm.getOperation()) {
+            case "!":
+                tracer = tracer.dive("case !");
+                if ((resultType != getAnyTypeReference()) && !resultType.isBooleanTypeReference()) {
+                    throw new IllegalArgumentException("'!(...)' expression requires boolean type. Actual type " + resultType);
+                }
+                return tracer + "!(" + toExpression(field, resultType, a, variableExpressionGenerator) + ")";
+            case "-":
+                tracer = tracer.dive("case -");
+                if ((resultType != getAnyTypeReference()) && !resultType.isIntegerTypeReference() && !resultType.isFloatTypeReference()) {
+                    throw new IllegalArgumentException("'-(...)' expression requires integer or floating-point type. Actual type " + resultType);
+                }
+                return tracer + "-(" + toExpression(field, resultType, a, variableExpressionGenerator) + ")";
+            case "()":
+                tracer = tracer.dive("case ()");
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ")";
+            default:
+                throw new RuntimeException("Unsupported unary operation type " + unaryTerm.getOperation() + ". Actual type " + resultType);
+        }
+    }
+
+    private String toBinaryTermExpression(Field field, TypeReference resultType, BinaryTerm binaryTerm, Function<VariableLiteral, String> variableExpressionGenerator, Tracer tracer) {
+        tracer = tracer.dive("binary term instanceOf");
+        Term a = binaryTerm.getA();
+        Term b = binaryTerm.getB();
+        String operation = binaryTerm.getOperation();
+        switch (operation) {
+            case "^": {
+                tracer = tracer.dive(operation);
+                if ((resultType != getAnyTypeReference()) && !resultType.isIntegerTypeReference() && !resultType.isFloatTypeReference()) {
+                    throw new IllegalArgumentException("'A^B' expression requires numeric result type. Actual type " + resultType);
+                }
+                return tracer + "Math.pow((" + toExpression(field, resultType, a, variableExpressionGenerator) + "), (" + toExpression(field, resultType, b, variableExpressionGenerator) + "))";
+            }
+            case "*":
+            case "/":
+            case "%":
+            case "+":
+            case "-": {
+                tracer = tracer.dive(operation);
+                if ((resultType != getAnyTypeReference()) && !resultType.isIntegerTypeReference() && !resultType.isFloatTypeReference()) {
+                    throw new IllegalArgumentException("'A" + operation + "B' expression requires numeric result type. Actual type " + resultType);
+                }
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, resultType, b, variableExpressionGenerator) + ")";
+            }
+            case ">>":
+            case "<<": {
+                tracer = tracer.dive(operation);
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, INT_TYPE_REFERENCE, b, variableExpressionGenerator) + ")";
+            }
+            case ">=":
+            case "<=":
+            case ">":
+            case "<":
+            case "==":
+            case "!=":
+                if ((resultType != getAnyTypeReference()) && !resultType.isBooleanTypeReference()) {
+                    throw new IllegalArgumentException("'A" + operation + "B' expression requires boolean result type. Actual type " + resultType);
+                }
+                // TODO: Try to infer the types of the arguments in this case
+                return tracer + "(" + toExpression(field, ANY_TYPE_REFERENCE, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, ANY_TYPE_REFERENCE, b, variableExpressionGenerator) + ")";
+            case "&&":
+            case "||":
+                if ((resultType != getAnyTypeReference()) && !resultType.isBooleanTypeReference()) {
+                    throw new IllegalArgumentException("'A" + operation + "B' expression requires boolean result type. Actual type " + resultType);
+                }
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, resultType, b, variableExpressionGenerator) + ")";
+            case "&":
+            case "|":
+                if ((resultType != getAnyTypeReference()) && !resultType.isIntegerTypeReference() && !resultType.isByteTypeReference()) {
+                    throw new IllegalArgumentException("'A" + operation + "B' expression requires byte or integer result type. Actual type " + resultType);
+                }
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, resultType, b, variableExpressionGenerator) + ")";
+            default:
+                throw new IllegalArgumentException("Unsupported ternary operation type " + operation);
+        }
+    }
+
+    private String toTernaryTermExpression(Field field, TypeReference resultType, TernaryTerm ternaryTerm, Function<VariableLiteral, String> variableExpressionGenerator, Tracer tracer) {
+        tracer = tracer.dive("ternary term instanceOf");
+        if ("if".equals(ternaryTerm.getOperation())) {
+            Term a = ternaryTerm.getA();
+            Term b = ternaryTerm.getB();
+            Term c = ternaryTerm.getC();
+            return tracer +
+                "(" +
+                "(" + toExpression(field, BOOL_TYPE_REFERENCE, a, variableExpressionGenerator) + ") ? " +
+                toExpression(field, resultType, b, variableExpressionGenerator) + " : " +
+                toExpression(field, resultType, c, variableExpressionGenerator) + "" +
+                ")";
+        } else {
+            throw new IllegalArgumentException("Unsupported ternary operation type " + ternaryTerm.getOperation() + ". Actual type " + resultType);
+        }
+    }
+
+    public String toVariableEnumAccessExpression(VariableLiteral variableLiteral) {
+        return variableLiteral.getName();
+    }
+
+    private String toVariableParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> parserArguments) {
+        Tracer tracer = Tracer.start("toVariableParseExpression");
+        // CAST expressions are special as we need to add a ".class" to the second parameter in Java.
+        if ("CAST".equals(variableLiteral.getName())) {
+            return toCastVariableParseExpression(field, resultType, variableLiteral, parserArguments, tracer);
+        }
+        // Special handling for ByteOrder enums (Built in enums)
+        else if ("BIG_ENDIAN".equals(variableLiteral.getName())) {
+            return "ByteOrder.BIG_ENDIAN";
+        } else if ("LITTLE_ENDIAN".equals(variableLiteral.getName())) {
+            return "ByteOrder.LITTLE_ENDIAN";
+        }
+        // If we're referencing an implicit field, we need to handle that differently.
+        else if (isVariableLiteralImplicitField(variableLiteral)) { // If we are accessing implicit fields, we need to rely on a local variable instead.
+            return toImplicitVariableParseExpression(field, resultType, variableLiteral, tracer);
+        }
+        // Call a static function in the drivers StaticHelper
+        else if ("STATIC_CALL".equals(variableLiteral.getName())) {
+            return toStaticCallParseExpression(field, resultType, variableLiteral, parserArguments, tracer);
+        }
+        // Call a built-in global static function
+        else if (variableLiteral.getName().equals(variableLiteral.getName().toUpperCase())) { // All uppercase names are not fields, but utility methods.
+            return toFunctionCallParseExpression(field, resultType, variableLiteral, parserArguments, tracer);
+        }
+        // The synthetic checksumRawData is a local field and should not be accessed as bean property.
+        boolean isParserArg = "readBuffer".equals(variableLiteral.getName());
+        boolean isTypeArg = "_type".equals(variableLiteral.getName());
+        if (!isParserArg && !isTypeArg && parserArguments != null) {
+            for (Argument serializerArgument : parserArguments) {
+                if (serializerArgument.getName().equals(variableLiteral.getName())) {
+                    isParserArg = true;
+                    break;
+                }
+            }
+        }
+        if (isParserArg) {
+            tracer = tracer.dive("parser arg");
+            return tracer + variableLiteral.getName() + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+        } else if (isTypeArg) {
+            tracer = tracer.dive("type arg");
+            String part = variableLiteral.getChild().map(VariableLiteral::getName).orElse("");
+            switch (part) {
+                case "name":
+                    return tracer + "\"" + field.getTypeName() + "\"";
+                case "length":
+                    return tracer + "\"" + ((SimpleTypeReference) field).getSizeInBits() + "\"";
+                case "encoding":
+                    String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                    return tracer + "\"" + encoding + "\"";
+                default:
+                    return tracer + "";
+            }
+        } else {
+            String indexAddon = "";
+            if (variableLiteral.getIndex().isPresent()) {
+                indexAddon = ".get(" + variableLiteral.getIndex().orElseThrow() + ")";
+            }
+            return tracer + variableLiteral.getName() + indexAddon + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+        }
+    }
+
+    private String toCastVariableParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> parserArguments, Tracer tracer) {
+        tracer = tracer.dive("CAST");
+        List<Term> arguments = variableLiteral.getArgs().orElseThrow(() -> new RuntimeException("A Cast expression needs arguments"));
+        if (arguments.size() != 2) {
+            throw new RuntimeException("A CAST expression expects exactly two arguments.");
+        }
+        VariableLiteral firstArgument = arguments.get(0).asLiteral()
+            .orElseThrow(() -> new RuntimeException("First argument should be a literal"))
+            .asVariableLiteral()
+            .orElseThrow(() -> new RuntimeException("First argument should be a Variable literal"));
+        StringLiteral typeArgument = arguments.get(1).asLiteral().orElseThrow(() -> new RuntimeException("Second argument should be a String literal"))
+            .asStringLiteral()
+            .orElseThrow(() -> new RuntimeException("Second argument should be a String literal"));
+        String sb = "CAST" + "(" +
+            toVariableParseExpression(field, ANY_TYPE_REFERENCE, firstArgument, parserArguments) +
+            ", " +
+            typeArgument.getValue() + ".class)";
+        return tracer + sb + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+    }
+
+    private String toImplicitVariableParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, Tracer tracer) {
+        tracer = tracer.dive("implicit");
+        return tracer + variableLiteral.getName();
+    }
+
+    private String toStaticCallParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> parserArguments, Tracer tracer) {
+        tracer = tracer.dive("STATIC_CALL");
+        List<Term> arguments = variableLiteral.getArgs().orElseThrow(() -> new RuntimeException("A STATIC_CALL expression needs arguments"));
+        if (arguments.size() < 1) {
+            throw new RuntimeException("A STATIC_CALL expression expects at least one argument.");
+        }
+        // TODO: make it as static import with a emitImport so if a static call is present a "utils" package must be present in the import
+        StringBuilder sb = new StringBuilder();
+        sb.append(packageName()).append(".utils.StaticHelper.");
+        // Get the class and method name
+        String methodName = arguments.get(0).asLiteral()
+            .orElseThrow(() -> new RuntimeException("First argument should be a literal"))
+            .asStringLiteral()
+            .orElseThrow(() -> new RuntimeException("Expecting the first argument of a 'STATIC_CALL' to be a StringLiteral")).
+            getValue();
+        sb.append(methodName).append("(");
+        for (int i = 1; i < arguments.size(); i++) {
+            Term arg = arguments.get(i);
+            if (i > 1) {
+                sb.append(", ");
+            }
+            sb.append(toParseExpression(field, ANY_TYPE_REFERENCE, arg, parserArguments));
+           /*if (arg instanceof VariableLiteral) {
+                VariableLiteral variableLiteralArg = (VariableLiteral) arg;
+                // "readBuffer" is the default name of the reader argument which is always available.
+                boolean isParserArg = "readBuffer".equals(variableLiteralArg.getName());
+                boolean isTypeArg = "_type".equals(variableLiteralArg.getName());
+                if (!isParserArg && !isTypeArg && parserArguments != null) {
+                    for (Argument parserArgument : parserArguments) {
+                        if (parserArgument.getName().equals(variableLiteralArg.getName())) {
+                            isParserArg = true;
+                            break;
+                        }
+                    }
+                }
+                if (isParserArg) {
+                    sb.append(variableLiteralArg.getName()).append(variableLiteralArg.getChild().map(child -> "." + toVariableExpressionRest(child)).orElse(""));
+                } else if (isTypeArg) {// We have to manually evaluate the type information at code-generation time.
+                    String part = variableLiteralArg.getChild().map(VariableLiteral::getName).orElse("");
+                    switch (part) {
+                        case "name":
+                            sb.append("\"").append(field.getTypeName()).append("\"");
+                            break;
+                        case "length":
+                            sb.append("\"").append(((SimpleTypeReference) field).getSizeInBits()).append("\"");
+                            break;
+                        case "encoding":
+                            String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                            sb.append("\"").append(encoding).append("\"");
+                            break;
+                    }
+                } else {
+                    sb.append(toVariableParseExpression(field, variableLiteralArg, null));
+                }
+            } else if (arg instanceof StringLiteral) {
+                sb.append(((StringLiteral) arg).getValue());
+            }*/
+        }
+        sb.append(")");
+        if (variableLiteral.getIndex().isPresent()) {
+            // TODO: If this is a byte typed field, this needs to be an array accessor instead.
+            sb.append(".get(").append(variableLiteral.getIndex().orElseThrow()).append(")");
+        }
+        return tracer + sb.toString();
+    }
+
+    private String toFunctionCallParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> parserArguments, Tracer tracer) {
+        tracer = tracer.dive("FunctionCall");
+        StringBuilder sb = new StringBuilder(variableLiteral.getName());
+        if (variableLiteral.getArgs().isPresent()) {
+            sb.append("(");
+            boolean firstArg = true;
+            for (Term arg : variableLiteral.getArgs().get()) {
+                if (!firstArg) {
+                    sb.append(", ");
+                }
+                // TODO: Try to infer the type of the argument ...
+                sb.append(toParseExpression(field, ANY_TYPE_REFERENCE, arg, parserArguments));
+                firstArg = false;
+            }
+            sb.append(")");
+        }
+        if (variableLiteral.getIndex().isPresent()) {
+            // TODO: If this is a byte typed field, this needs to be an array accessor instead.
+            sb.append(".get(").append(variableLiteral.getIndex().orElseThrow()).append(")");
+        }
+        return tracer + sb.toString() + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+    }
+
+    private String toVariableSerializationExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> serialzerArguments) {
+        Tracer tracer = Tracer.start("variable serialization expression");
+        if ("STATIC_CALL".equals(variableLiteral.getName())) {
+            return toStaticCallSerializationExpression(field, resultType, variableLiteral, serialzerArguments, tracer);
+        }
+        // All uppercase names are not fields, but utility methods.
+        else if (variableLiteral.getName().equals(variableLiteral.getName().toUpperCase())) {
+            return toGlobalFunctionCallSerializationExpression(field, resultType, variableLiteral, serialzerArguments, tracer);
+        } else if (isVariableLiteralImplicitField(variableLiteral)) { // If we are accessing implicit fields, we need to rely on a local variable instead.
+            tracer = tracer.dive("implicit field");
+            final ImplicitField referencedImplicitField = getReferencedImplicitField(variableLiteral);
+            return tracer + toSerializationExpression(referencedImplicitField, referencedImplicitField.getType(), getReferencedImplicitField(variableLiteral).getSerializeExpression(), serialzerArguments);
+        } else if (isVariableLiteralVirtualField(variableLiteral)) {
+            tracer = tracer.dive("virtual field");
+            return tracer + toVariableExpressionRest(field, resultType, variableLiteral);
+        }
+        // The synthetic checksumRawData is a local field and should not be accessed as bean property.
+        boolean isSerializerArg = "writeBuffer".equals(variableLiteral.getName()) || "checksumRawData".equals(variableLiteral.getName()) || "_value".equals(variableLiteral.getName()) || "element".equals(variableLiteral.getName()) || "size".equals(variableLiteral.getName());
+        boolean isTypeArg = "_type".equals(variableLiteral.getName());
+        if (!isSerializerArg && !isTypeArg && serialzerArguments != null) {
+            for (Argument serializerArgument : serialzerArguments) {
+                if (serializerArgument.getName().equals(variableLiteral.getName())) {
+                    isSerializerArg = true;
+                    break;
+                }
+            }
+        }
+        if (isSerializerArg) {
+            tracer = tracer.dive("serializer arg");
+            return tracer + variableLiteral.getName() + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+        } else if (isTypeArg) {
+            tracer = tracer.dive("type arg");
+            String part = variableLiteral.getChild().map(VariableLiteral::getName).orElse("");
+            switch (part) {
+                case "name":
+                    return tracer + "\"" + field.getTypeName() + "\"";
+                case "length":
+                    return tracer + "\"" + ((SimpleTypeReference) field).getSizeInBits() + "\"";
+                case "encoding":
+                    String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                    return tracer + "\"" + encoding + "\"";
+                default:
+                    return tracer + "";
+            }
+        } else {
+            return tracer + toVariableExpressionRest(field, resultType, variableLiteral);
+        }
+    }
+
+    private String toGlobalFunctionCallSerializationExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> serialzerArguments, Tracer tracer) {
+        tracer = tracer.dive("GLOBAL_FUNCTION_CALL");
+        StringBuilder sb = new StringBuilder(variableLiteral.getName());
+        if (variableLiteral.getArgs().isPresent()) {
+            sb.append("(");
+            boolean firstArg = true;
+            for (Term arg : variableLiteral.getArgs().get()) {
+                if (!firstArg) {
+                    sb.append(", ");
+                }
+                sb.append(toSerializationExpression(field, ANY_TYPE_REFERENCE, arg, serialzerArguments));
+                firstArg = false;
+                /*if (arg instanceof VariableLiteral) {
+                    VariableLiteral va = (VariableLiteral) arg;
+                    boolean isSerializerArg = "readBuffer".equals(va.getName()) || "writeBuffer".equals(va.getName());
+                    boolean isTypeArg = "_type".equals(va.getName());
+                    if (!isSerializerArg && !isTypeArg && serialzerArguments != null) {
+                        for (Argument serializerArgument : serialzerArguments) {
+                            if (serializerArgument.getName().equals(va.getName())) {
+                                isSerializerArg = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (isSerializerArg) {
+                        sb.append(va.getName()).append(va.getChild().map(child -> "." + toVariableExpressionRest(child)).orElse(""));
+                    } else if (isTypeArg) {
+                        String part = va.getChild().map(VariableLiteral::getName).orElse("");
+                        switch (part) {
+                            case "name":
+                                sb.append("\"").append(field.getTypeName()).append("\"");
+                                break;
+                            case "length":
+                                sb.append("\"").append(((SimpleTypeReference) field).getSizeInBits()).append("\"");
+                                break;
+                            case "encoding":
+                                String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                                sb.append("\"").append(encoding).append("\"");
+                                break;
+                        }
+                    } else {
+                        sb.append(toVariableSerializationExpression(field, va, serialzerArguments));
+                    }
+                } else if (arg instanceof StringLiteral) {
+                    sb.append(((StringLiteral) arg).getValue());
+                }*/
+            }
+            sb.append(")");
+        }
+        return tracer + sb.toString();
+    }
+
+    private String toStaticCallSerializationExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> serialzerArguments, Tracer tracer) {
+        tracer = tracer.dive("STATIC_CALL");
+        StringBuilder sb = new StringBuilder();
+        List<Term> arguments = variableLiteral.getArgs().orElseThrow(() -> new RuntimeException("A STATIC_CALL expression needs arguments"));
+        if (arguments.size() < 1) {
+            throw new RuntimeException("A STATIC_CALL expression expects at least one argument.");
+        }
+        // TODO: make it as static import with a emitImport so if a static call is present a "utils" package must be present in the import
+        sb.append(packageName()).append(".utils.StaticHelper.");
+        // Get the class and method name
+        String methodName = arguments.get(0).asLiteral()
+            .orElseThrow(() -> new RuntimeException("First argument should be a literal"))
+            .asStringLiteral()
+            .orElseThrow(() -> new RuntimeException("Expecting the first argument of a 'STATIC_CALL' to be a StringLiteral")).
+            getValue();
+        //methodName = methodName.substring(1, methodName.length() - 1);
+        sb.append(methodName).append("(");
+        for (int i = 1; i < arguments.size(); i++) {
+            Term arg = arguments.get(i);
+            if (i > 1) {
+                sb.append(", ");
+            }
+            sb.append(toSerializationExpression(field, ANY_TYPE_REFERENCE, arg, serialzerArguments));
+            /*if (arg instanceof VariableLiteral) {
+                VariableLiteral va = (VariableLiteral) arg;
+                // "readBuffer" and "_value" are always available in every parser.
+                boolean isSerializerArg = "readBuffer".equals(va.getName()) || "writeBuffer".equals(va.getName()) || "_value".equals(va.getName()) || "element".equals(va.getName());
+                boolean isTypeArg = "_type".equals(va.getName());
+                if (!isSerializerArg && !isTypeArg && serialzerArguments != null) {
+                    for (Argument serializerArgument : serialzerArguments) {
+                        if (serializerArgument.getName().equals(va.getName())) {
+                            isSerializerArg = true;
+                            break;
+                        }
+                    }
+                }
+                if (isSerializerArg) {
+                    sb.append(va.getName()).append(va.getChild().map(child -> "." + toVariableExpressionRest(child)).orElse(""));
+                } else if (isTypeArg) {
+                    String part = va.getChild().map(VariableLiteral::getName).orElse("");
+                    switch (part) {
+                        case "name":
+                            sb.append("\"").append(field.getTypeName()).append("\"");
+                            break;
+                        case "length":
+                            sb.append("\"").append(((SimpleTypeReference) field).getSizeInBits()).append("\"");
+                            break;
+                        case "encoding":
+                            String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                            sb.append("\"").append(encoding).append("\"");
+                            break;
+                    }
+                } else {
+                    sb.append(toVariableSerializationExpression(field, va, serialzerArguments));
+                }
+            } else if (arg instanceof StringLiteral) {
+                sb.append(((StringLiteral) arg).getValue());
+            }*/
+        }
+        sb.append(")");
+        return tracer + sb.toString();
+    }
+
+    private String toVariableExpressionRest(Field field, TypeReference resultType, VariableLiteral variableLiteral) {
+        Tracer tracer = Tracer.start("variable expression rest");
+        // length is kind of a keyword in mspec, so we shouldn't be naming variables length. if we ask for the length of a object we can just return length().
+        // This way we can get the length of a string when serializing
+        String variableLiteralName = variableLiteral.getName();
+        if (variableLiteralName.equals("length")) {
+            tracer = tracer.dive("length");
+            return tracer + variableLiteralName + "()" + ((variableLiteral.getIndex().isPresent() ? ".get(" + variableLiteral.getIndex().orElseThrow() + ")" : "") +
+                variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse(""));
+        }
+        return tracer + "get" + WordUtils.capitalize(variableLiteralName) + "()" + ((variableLiteral.getIndex().isPresent() ? ".get(" + variableLiteral.getIndex().orElseThrow() + ")" : "") +
+            variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse(""));
+    }
+
+    public String getSizeInBits(ComplexTypeDefinition complexTypeDefinition, List<Argument> parserArguments) {
+        int sizeInBits = 0;
+        StringBuilder sb = new StringBuilder();
+        for (Field field : complexTypeDefinition.getFields()) {
+            if (field instanceof ArrayField) {
+                ArrayField arrayField = (ArrayField) field;
+                final SimpleTypeReference type = (SimpleTypeReference) arrayField.getType();
+                switch (arrayField.getLoopType()) {
+                    case COUNT:
+                        sb.append("(").append(toSerializationExpression(null, INT_TYPE_REFERENCE, arrayField.getLoopExpression(), parserArguments)).append(" * ").append(type.getSizeInBits()).append(") + ");
+                        break;
+                    case LENGTH:
+                        sb.append("(").append(toSerializationExpression(null, INT_TYPE_REFERENCE, arrayField.getLoopExpression(), parserArguments)).append(" * 8) + ");
+                        break;
+                    case TERMINATED:
+                        // No terminated.
+                        break;
+                }
+            } else if (field instanceof TypedField) {
+                TypedField typedField = (TypedField) field;
+                final TypeReference type = typedField.getType();
+                if (field instanceof ManualField) {
+                    ManualField manualField = (ManualField) field;
+                    sb.append("(").append(toSerializationExpression(null, INT_TYPE_REFERENCE, manualField.getLengthExpression(), parserArguments)).append(") + ");
+                } else if (type instanceof SimpleTypeReference) {
+                    SimpleTypeReference simpleTypeReference = (SimpleTypeReference) type;
+                    if (simpleTypeReference instanceof VstringTypeReference) {
+                        sb.append(toSerializationExpression(null, INT_TYPE_REFERENCE, ((VstringTypeReference) simpleTypeReference).getLengthExpression(), parserArguments)).append(" + ");
+                    } else {
+                        sizeInBits += simpleTypeReference.getSizeInBits();
+                    }
+                }
+            }
+        }
+        return sb.toString() + sizeInBits;
+    }
+
+    public String escapeValue(TypeReference typeReference, String valueString) {
+        if (valueString == null) {
+            return null;
+        }
+        if (typeReference instanceof SimpleTypeReference) {
+            SimpleTypeReference simpleTypeReference = (SimpleTypeReference) typeReference;
+            switch (simpleTypeReference.getBaseType()) {
+                case UINT:
+                case INT:
+                    // If it's a one character string and is numeric, output it as char.
+                    if (!NumberUtils.isParsable(valueString) && (valueString.length() == 1)) {
+                        return "'" + valueString + "'";
+                    }
+                    break;
+                case STRING:
+                case VSTRING:
+                    return "\"" + valueString + "\"";
+            }
+        }
+        return valueString;
+    }
+
+    public String getFieldOptions(TypedField field, List<Argument> parserArguments) {
+        StringBuilder sb = new StringBuilder();
+        final Optional<Term> encodingOptional = field.getEncoding();
+        if (encodingOptional.isPresent()) {
+            final String encoding = toParseExpression(field, field.getType(), encodingOptional.get(), parserArguments);
+            sb.append(", WithOption.WithEncoding(").append(encoding).append(")");
+        }
+        final Optional<Term> byteOrderOptional = field.getByteOrder();
+        if (byteOrderOptional.isPresent()) {
+            final String byteOrder = toParseExpression(field, field.getType(), byteOrderOptional.get(), parserArguments);
+            sb.append(", WithOption.WithByteOrder(").append(byteOrder).append(")");
+        }
+        return sb.toString();
+    }
+
+    public boolean isBigIntegerSource(Term term) {
+        boolean isBigInteger = term.asLiteral()
+            .flatMap(LiteralConversions::asVariableLiteral)
+            .flatMap(VariableLiteral::getChild)
+            .map(Term.class::cast)
+            .map(this::isBigIntegerSource)
+            .orElse(false);
+        return isBigInteger || term.asLiteral()
+            .flatMap(LiteralConversions::asVariableLiteral)
+            .map(VariableLiteral::getTypeReference)
+            .flatMap(TypeReferenceConversions::asIntegerTypeReference)
+            .map(integerTypeReference -> integerTypeReference.getSizeInBits() >= 64)
+            .orElse(false);
+    }
+
+    public boolean needsLongMarker(Optional<SimpleTypeReference> baseTypeReference) {
+        return baseTypeReference.isPresent() && baseTypeReference.get().isIntegerTypeReference() && baseTypeReference.get().asIntegerTypeReference().orElseThrow().getSizeInBits() >= 32;
+    }
+
+}
diff --git a/code-generation/language-python/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.language.LanguageOutput b/code-generation/language-python/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.language.LanguageOutput
new file mode 100644
index 0000000000..6af10b1ebd
--- /dev/null
+++ b/code-generation/language-python/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.language.LanguageOutput
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+org.apache.plc4x.language.python.PythonLanguageOutput
diff --git a/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh
new file mode 100644
index 0000000000..c712b97723
--- /dev/null
+++ b/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh
@@ -0,0 +1,977 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<#-- Prevent freemarker from escaping stuff -->
+<#outputformat "undefined">
+<#-- Declare the name and type of variables passed in to the template -->
+<#-- @ftlvariable name="languageName" type="java.lang.String" -->
+<#-- @ftlvariable name="protocolName" type="java.lang.String" -->
+<#-- @ftlvariable name="outputFlavor" type="java.lang.String" -->
+<#-- @ftlvariable name="helper" type="org.apache.plc4x.language.java.JavaLanguageTemplateHelper" -->
+<#-- @ftlvariable name="tracer" type="org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer" -->
+<#-- @ftlvariable name="type" type="org.apache.plc4x.plugins.codegenerator.types.definitions.ComplexTypeDefinition" -->
+<#-- Declare the name and type of variables declared locally inside the template -->
+${helper.packageName(protocolName, languageName, outputFlavor)?replace(".", "/")}/${type.name}.java
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package ${helper.packageName(protocolName, languageName, outputFlavor)};
+
+import static org.apache.plc4x.java.spi.generation.StaticHelper.*;
+import static org.apache.plc4x.java.spi.codegen.io.DataReaderFactory.*;
+import static org.apache.plc4x.java.spi.codegen.io.DataWriterFactory.*;
+import static org.apache.plc4x.java.spi.codegen.fields.FieldReaderFactory.*;
+import static org.apache.plc4x.java.spi.codegen.fields.FieldWriterFactory.*;
+
+import org.apache.plc4x.java.spi.codegen.*;
+import org.apache.plc4x.java.spi.codegen.io.*;
+import org.apache.plc4x.java.spi.codegen.fields.*;
+import org.apache.plc4x.java.api.exceptions.*;
+import org.apache.plc4x.java.spi.generation.*;
+import org.apache.plc4x.java.api.value.*;
+
+import java.time.*;
+import java.util.*;
+import java.math.BigInteger;
+
+// Code generated by code-generation. DO NOT EDIT.
+
+<#-- TODO: the code below implies that parserArguments will be null if not present... not pretty  -->
+<#if type.parserArguments.isPresent()><#assign parserArguments=type.allParserArguments.orElseThrow()></#if>
+public<#if type.isDiscriminatedParentTypeDefinition()> abstract</#if> class ${type.name}<#if type.isDiscriminatedParentTypeDefinition()></#if><#if type.parentType.isPresent()> extends ${type.parentType.orElseThrow().name}</#if> implements Message {
+
+<#--
+    If this is a discriminated child type, we need to generate methods for accessing it's discriminator
+    values, as if they were normal java properties.
+-->
+<#if type.isDiscriminatedChildTypeDefinition()>
+    <#assign discriminatedChildType = type.asDiscriminatedComplexTypeDefinition().orElseThrow()>
+    // Accessors for discriminator values.
+    <#list discriminatedChildType.getDiscriminatorMap() as discriminatorName, discriminatorValue>
+        <#-- If the discriminator name matches that of another field, suppress the methods generation -->
+        <#if !discriminatedChildType.isNonDiscriminatorField(discriminatorName)><#--&& !discriminatedChildType.isParserArgument(discriminatorName)-->
+            <#assign discriminatorType = helper.getDiscriminatorTypes()[discriminatorName]>
+    public ${helper.getLanguageTypeNameForTypeReference(discriminatorType)} get${discriminatorName?cap_first}() {
+            <#if discriminatorValue?? && !helper.isWildcard(discriminatorValue)>
+                <#if discriminatorType.isEnumTypeReference()>
+        return ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(null, discriminatorType, discriminatorValue, parserArguments)};
+                <#else>
+        return (${helper.getLanguageTypeNameForTypeReference(discriminatorType, true)}) ${helper.toParseExpression(null, discriminatorType, discriminatorValue, parserArguments)};
+                </#if>
+            <#else>
+        return ${helper.getNullValueForTypeReference(discriminatorType)};
+            </#if>
+    }
+        </#if>
+    </#list>
+</#if>
+<#--
+    If this is a discriminated parent type, we need to generate the abstract methods for accessing it's
+    discriminator values instead.
+-->
+<#if type.isDiscriminatedParentTypeDefinition()>
+    <#assign discriminatedParentType = type>
+    <#-- @ftlvariable name="discriminatedParentType" type="org.apache.plc4x.plugins.codegenerator.types.definitions.ComplexTypeDefinition" -->
+    // Abstract accessors for discriminator values.
+    <#list helper.discriminatorTypes as discriminatorName, discriminatorType>
+        <#-- If the discriminator name matches that of another field, suppress the methods generation -->
+        <#if !type.isNonDiscriminatorField(discriminatorName)><#-- && !type.isParserArgument(discriminatorName)-->
+    public abstract ${helper.getLanguageTypeNameForTypeReference(discriminatorType)} get${discriminatorName?cap_first}();
+        </#if>
+    </#list>
+</#if>
+<#-- If the current type contains "const" fields, generate some java constants for holing their values -->
+<#if type.constFields?has_content>
+
+    // Constant values.
+    <#list type.constFields as field>
+    public static final ${helper.getLanguageTypeNameForTypeReference(field.type)} ${field.name?upper_case} = ${helper.toParseExpression(field, field.type, field.referenceValue, parserArguments)};
+    </#list>
+</#if>
+<#-- Property fields are fields that require a property in the pojo -->
+<#if type.propertyFields?has_content>
+
+    // Properties.
+    <#list type.propertyFields as field>
+    protected final ${helper.getLanguageTypeNameForTypeReference(field.type, !field.isOptionalField())} ${field.name};
+    </#list>
+</#if>
+<#if parserArguments?has_content>
+  <#assign filteredParserArguments=parserArguments?filter(arg -> !type.isDiscriminatorField(arg.name) && !type.getPropertyFieldFromThisOrParentByName(arg.name).isPresent())>
+</#if>
+<#if filteredParserArguments?has_content>
+
+    // Arguments.
+    <#list filteredParserArguments as parserArgument>
+    protected final ${helper.getLanguageTypeNameForTypeReference(parserArgument.type)} ${parserArgument.name};
+    </#list>
+</#if>
+<#assign reservedFields=type.getFields()?filter(f->f.isReservedField())>
+<#if reservedFields?has_content>
+    // Reserved Fields
+    <#list reservedFields as reservedField>
+    private ${helper.getLanguageTypeNameForTypeReference(reservedField.asReservedField().orElseThrow().type, false)} reservedField${reservedField?index};
+    </#list>
+</#if>
+
+    <#-- getAllPropertyFields() returns not only the property fields of this type but also of it's parents -->
+    <@compress single_line=true>
+    public ${type.name}(
+        <#list type.getAllPropertyFields() as field>
+            ${helper.getLanguageTypeNameForField(field)} ${field.name}
+            <#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.getAllPropertyFields()?has_content>, </#if>
+            <#list filteredParserArguments as parserArgument>
+                ${helper.getLanguageTypeNameForTypeReference(parserArgument.type)} ${parserArgument.name}
+                <#sep>, </#sep>
+            </#list>
+        </#if>
+        ) {
+    </...@compress>
+
+    <@compress single_line=true>
+        super(
+        <#if type.parentPropertyFields?has_content>
+            <#list type.parentPropertyFields as field>
+                ${field.name}
+                <#sep>, </#sep>
+            </#list>
+        </#if>
+        <#if type.parentType.isPresent() && type.parentType.orElseThrow().allParserArguments.isPresent()>
+            <#assign filteredParentParserArguments = type.parentType.orElseThrow().allParserArguments.orElseThrow()?filter(arg -> !type.parentType.orElseThrow().asComplexTypeDefinition().orElseThrow().isDiscriminatorField(arg.name))>
+            <#if filteredParentParserArguments?has_content>
+                <#if type.parentPropertyFields?has_content>, </#if>
+                <#list filteredParentParserArguments as parserArgument>
+                    ${parserArgument.name}
+                    <#sep>, </#sep>
+                </#list>
+            </#if>
+        </#if>
+        );
+    </...@compress>
+
+<#list type.propertyFields as field>
+        this.${field.name} = ${field.name};
+</#list>
+<#if filteredParserArguments?has_content>
+    <#list filteredParserArguments as parserArgument>
+        this.${parserArgument.name} = ${parserArgument.name};
+    </#list>
+</#if>
+    }
+
+<#list type.abstractFields as field>
+    public abstract ${helper.getLanguageTypeNameForField(field)} get${field.asNamedField().orElseThrow().name?cap_first}();
+
+</#list>
+<#list type.propertyFields as field>
+    public ${helper.getLanguageTypeNameForField(field)} get${field.name?cap_first}() {
+        return ${field.name};
+    }
+
+</#list>
+<#list type.virtualFields as field>
+    public ${helper.getLanguageTypeNameForField(field)} get${field.name?cap_first}() {
+        <#if helper.getLanguageTypeNameForField(field) = 'String'>
+        return ${helper.getLanguageTypeNameForField(field)}.valueOf(${helper.toSerializationExpression(field, field.type, field.valueExpression, parserArguments)});
+        <#--elseif helper.getLanguageTypeNameForField(field) = 'BigInteger' && !helper.isBigIntegerSource(field.valueExpression)-->
+        <#elseif helper.getLanguageTypeNameForField(field) = 'BigInteger'>
+        Object o = ${helper.toSerializationExpression(field, field.type, field.valueExpression, parserArguments)};
+        if (o instanceof BigInteger)
+            return (BigInteger) o;
+        return BigInteger.valueOf(((Number)o).longValue());
+        <#else>
+        return (${helper.getLanguageTypeNameForField(field)}) (${helper.toSerializationExpression(field, field.type, field.valueExpression, parserArguments)});
+        </#if>
+    }
+
+</#list>
+<#list type.constFields as field>
+    public ${helper.getLanguageTypeNameForField(field)} get${field.name?cap_first}() {
+        return ${field.name?upper_case};
+    }
+
+</#list>
+
+    <#if outputFlavor != "passive">
+<#if type.isDiscriminatedChildTypeDefinition()>
+    @Override
+    protected void serialize${type.parentType.orElseThrow().name}Child(WriteBuffer writeBuffer) throws SerializationException {
+<#else>
+    <#if type.isDiscriminatedParentTypeDefinition()>
+    abstract protected void serialize${type.name?cap_first}Child(WriteBuffer writeBuffer) throws SerializationException;
+
+    </#if>
+    public void serialize(WriteBuffer writeBuffer) throws SerializationException {
+</#if>
+        PositionAware positionAware = writeBuffer;
+        <#if helper.hasFieldOfType("unknown")>
+            throw new SerializationException("Unknown field not serializable");
+        <#else>
+            int startPos = positionAware.getPos();
+            writeBuffer.pushContext("${type.name}");
+            <#assign reservedFieldIndex=0>
+            <#list type.fields as field>
+                <#switch field.typeName>
+                    <#case "array">
+                        <#assign arrayField = field.asArrayField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Array Field (${arrayField.name})
+                        <#if arrayField.type.elementTypeReference.isByteBased()>
+                        writeByteArrayField("${namedField.name}", ${namedField.name}, writeByteArray(writeBuffer, 8));
+                        <#elseif arrayField.type.elementTypeReference.isSimpleTypeReference()>
+                        writeSimpleTypeArrayField("${namedField.name}", ${namedField.name}, ${helper.getDataWriterCall(arrayField.type.elementTypeReference, namedField.name)});
+                        <#else>
+                        writeComplexTypeArrayField("${namedField.name}", ${namedField.name}, writeBuffer);
+                        </#if>
+                        <#break>
+                    <#case "checksum">
+                        <#assign checksumField = field.asChecksumField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Checksum Field (checksum) (Calculated)
+                        writeChecksumField("${namedField.name}", (${helper.getLanguageTypeNameForField(field)}) (${helper.toParseExpression(checksumField, checksumField.type, checksumField.checksumExpression, parserArguments)}), ${helper.getDataWriterCall(typedField.type, namedField.name)});
+                        <#break>
+                    <#case "const">
+                        <#assign constField = field.asConstField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Const Field (${constField.name})
+                        <#if typedField.type.isEnumTypeReference()>writeConstField("${constField.name}", ${namedField.name?upper_case}.getValue(), ${helper.getDataWriterCall(helper.getEnumBaseTypeReference(typedField.type), namedField.name)});<#else>writeConstField("${constField.name}", ${namedField.name?upper_case}, ${helper.getDataWriterCall(typedField.type, namedField.name)});</#if>
+                        <#break>
+                    <#case "discriminator">
+                        <#assign discriminatorField = field.asDiscriminatorField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Discriminator Field (${discriminatorField.name}) (Used as input to a switch field)
+                        <#if typedField.type.isEnumTypeReference()>writeDiscriminatorEnumField("${namedField.name}", "${helper.getLanguageTypeNameForField(field)}", get${discriminatorField.name?cap_first}(), ${helper.getEnumDataWriterCall(typedField.type, namedField.name, "value")});<#else>writeDiscriminatorField("${namedField.name}", get${discriminatorField.name?cap_first}(), ${helper.getDataWriterCall(typedField.type, namedField.name)});</#if>
+                        <#break>
+                    <#case "enum">
+                        <#assign enumField = field.asEnumField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Enum field (${namedField.name})
+                        writeEnumField("${namedField.name}", "${helper.getLanguageTypeNameForField(field)}", ${namedField.name}, ${helper.getEnumDataWriterCall(typedField.type, namedField.name, enumField.fieldName)});
+                        <#break>
+                    <#case "implicit">
+                        <#assign implicitField = field.asImplicitField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Implicit Field (${implicitField.name}) (Used for parsing, but its value is not stored as it's implicitly given by the objects content)
+                        <#-- Implicit field values might be used in expressions, in order to avoid problems, we generate a temporary variable with the given name. -->
+                        ${helper.getLanguageTypeNameForField(field)} ${implicitField.name} = (${helper.getLanguageTypeNameForField(field)}) (${helper.toSerializationExpression(implicitField, implicitField.type, implicitField.serializeExpression, parserArguments)});
+                        writeImplicitField("${namedField.name}", ${implicitField.name}, ${helper.getDataWriterCall(typedField.type, namedField.name)});
+                        <#break>
+                    <#case "manualArray">
+                        <#assign manualArrayField = field.asManualArrayField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Manual Array Field (${manualArrayField.name})
+                        writeManualArrayField("${namedField.name}", ${namedField.name}, (${helper.getLanguageTypeNameForTypeReference(manualArrayField.type.elementTypeReference)} _value) -> ${helper.toParseExpression(manualArrayField, manualArrayField.type.elementTypeReference, manualArrayField.serializeExpression, parserArguments)}, writeBuffer);
+                        <#break>
+                    <#case "manual">
+                        <#assign manualField = field.asManualField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Manual Field (${manualField.name})
+                        writeManualField("${namedField.name}", () -> ${helper.toParseExpression(manualField, manualField.type, manualField.serializeExpression, parserArguments)}, writeBuffer);
+                        <#break>
+                    <#case "optional">
+                        <#assign optionalField = field.asOptionalField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Optional Field (${optionalField.name}) (Can be skipped, if the value is null)
+                        <#if optionalField.type.isEnumTypeReference()>
+                            writeOptionalEnumField("${optionalField.name}", "${helper.getLanguageTypeNameForField(field)}", ${optionalField.name}, ${helper.getEnumDataWriterCall(optionalField.type, optionalField.name, "value")}<#if optionalField.conditionExpression.present>, ${helper.toSerializationExpression(optionalField, helper.boolTypeReference, optionalField.conditionExpression.get(), parserArguments)}</#if>);
+                        <#elseif optionalField.type.isDataIoTypeReference()>
+                            writeOptionalField("${optionalField.name}", ${optionalField.name}, new DataWriterDataIoDefault(writeBuffer, (wb, val) -> ${optionalField.type.asComplexTypeReference().orElseThrow().name}.staticSerialize(wb, val<#if optionalField.type.asComplexTypeReference().orElseThrow().params?has_content>, <#list optionalField.type.asComplexTypeReference().orElseThrow().params.orElseThrow() as param>${helper.toParseExpression(optionalField, helper.anyTypeReference, param, p [...]
+                        <#else>
+                            writeOptionalField("${optionalField.name}", ${optionalField.name}, ${helper.getDataWriterCall(typedField.type, optionalField.name)}<#if optionalField.conditionExpression.present>, ${helper.toSerializationExpression(optionalField, helper.boolTypeReference, optionalField.conditionExpression.get(), parserArguments)}</#if>);
+                        </#if>
+                        <#break>
+                    <#case "padding">
+                        <#assign paddingField = field.asPaddingField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+
+                        // Padding Field (padding)
+                        writePaddingField("padding", (int) (${helper.toParseExpression(paddingField, helper.intTypeReference, paddingField.paddingCondition, parserArguments)}), (${helper.getLanguageTypeNameForField(field)}) ${helper.toSerializationExpression(paddingField, paddingField.type, paddingField.paddingValue, parserArguments)}, ${helper.getDataWriterCall(typedField.type, "padding")});
+                        <#break>
+                    <#case "reserved">
+                        <#assign reservedField = field.asReservedField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+
+                        // Reserved Field (reserved)
+                        writeReservedField("reserved", reservedField${reservedFieldIndex}!=null?reservedField${reservedFieldIndex}:${helper.getReservedValue(reservedField)}, ${helper.getDataWriterCall(typedField.type, "reserved")});<#assign reservedFieldIndex=reservedFieldIndex+1>
+                        <#break>
+                    <#case "simple">
+                        <#assign simpleField = field.asSimpleField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Simple Field (${namedField.name})
+                        <#if typedField.type.isEnumTypeReference()>
+                            writeSimpleEnumField("${simpleField.name}", "${helper.getLanguageTypeNameForField(field)}", ${simpleField.name}, ${helper.getEnumDataWriterCall(simpleField.type, simpleField.name, "value")});
+                        <#elseif simpleField.type.isDataIoTypeReference()>
+                            writeSimpleField("${simpleField.name}", ${simpleField.name}, new DataWriterDataIoDefault(writeBuffer, (wb, val) -> ${simpleField.type.asComplexTypeReference().orElseThrow().name}.staticSerialize(wb, val<#if simpleField.type.asComplexTypeReference().orElseThrow().params?has_content>, <#list simpleField.type.asComplexTypeReference().orElseThrow().params.orElseThrow() as param>${helper.toParseExpression(simpleField, helper.anyTypeReference, param, parserArguments [...]
+                        <#else>
+                            writeSimpleField("${simpleField.name}", ${simpleField.name}, ${helper.getDataWriterCall(typedField.type, simpleField.name)}${helper.getFieldOptions(typedField, parserArguments)});</#if>
+                        <#break>
+                    <#case "switch">
+                        <#assign switchField = field.asSwitchField().orElseThrow()>
+
+                        // Switch field (Serialize the sub-type)
+                        serialize${type.name?cap_first}Child(writeBuffer);
+                        <#break>
+                    <#case "virtual">
+                        <#assign virtualField = field.asVirtualField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Virtual field (doesn't actually serialize anything, just makes the value available)
+                        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = get${namedField.name?cap_first}();
+                        writeBuffer.writeVirtual("${namedField.name}", ${namedField.name});
+                        <#break>
+                </#switch>
+            </#list>
+
+            writeBuffer.popContext("${type.name}");
+        </#if>
+        }
+    </#if>
+
+    @Override
+    public int getLengthInBytes() {
+        return (int) Math.ceil((float) getLengthInBits() / 8.0);
+    }
+
+    @Override
+    public int getLengthInBits() {
+        int lengthInBits = <#if type.parentType.isPresent()>super.getLengthInBits()<#else>0</#if>;
+        ${type.name} _value  = this;
+<#list type.fields as field>
+<#switch field.typeName>
+    <#case "array">
+        <#assign arrayField = field.asArrayField().orElseThrow()>
+        <#assign arrayElementTypeReference = arrayField.type.asArrayTypeReference().orElseThrow().getElementTypeReference()>
+
+        // Array field
+        if(${arrayField.name} != null) {
+        <#if arrayElementTypeReference.isSimpleTypeReference()>
+            <#assign simpleTypeReference = arrayElementTypeReference.asSimpleTypeReference().orElseThrow()>
+            lengthInBits += ${simpleTypeReference.sizeInBits} * ${arrayField.name}.<#if arrayElementTypeReference.isByteBased()>length<#else>size()</#if>;
+        <#elseif arrayField.isCountArrayField()>
+            int i=0;
+            <#assign nonSimpleTypeReference = arrayElementTypeReference.asNonSimpleTypeReference().orElseThrow()>
+            for(${nonSimpleTypeReference.name} element : ${arrayField.name}) {
+                boolean last = ++i >= ${arrayField.name}.size();
+                lengthInBits += element.getLengthInBits();
+            }
+        <#else>
+            for(Message element : ${arrayField.name}) {
+                lengthInBits += element.getLengthInBits();
+            }
+        </#if>
+        }
+        <#break>
+    <#case "checksum">
+        <#assign checksumField = field.asChecksumField().orElseThrow()>
+        <#assign typedField = field.asTypedField().orElseThrow()>
+        <#assign simpleTypeReference = typedField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Checksum Field (checksum)
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#break>
+    <#case "const">
+        <#assign constField = field.asConstField().orElseThrow()>
+        <#assign typedField = field.asTypedField().orElseThrow()>
+
+        // Const Field (${constField.name})
+        <#if typedField.type.isSimpleTypeReference()>
+        <#assign simpleTypeReference = typedField.type.asSimpleTypeReference().orElseThrow()>
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#else>
+        lengthInBits += ${helper.getEnumBaseTypeReference(typedField.type).sizeInBits};
+        </#if>
+        <#break>
+    <#case "discriminator">
+        <#assign discriminatorField = field.asDiscriminatorField().orElseThrow()>
+
+        // Discriminator Field (${discriminatorField.name})
+        <#if discriminatorField.type.isSimpleTypeReference()>
+            <#assign simpleTypeReference = discriminatorField.type.asSimpleTypeReference().orElseThrow()>
+            <#if simpleTypeReference.isVstringTypeReference()>
+                <#assign vstringTypeReference = simpleTypeReference.asVstringTypeReference().orElseThrow()>
+        lengthInBits += ${helper.toSerializationExpression(discriminatorField, helper.intTypeReference, vstringTypeReference.getLengthExpression(), parserArguments)};
+            <#else>
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+            </#if>
+        <#elseif helper.isEnumField(field)>
+            lengthInBits += ${helper.getEnumBaseTypeReference(discriminatorField.type).sizeInBits};
+        <#else>
+            lengthInBits += ${discriminatorField.name}.getLengthInBits();
+        </#if>
+        <#break>
+    <#case "enum">
+        <#assign enumField = field.asEnumField().orElseThrow()>
+
+        // Enum Field (${enumField.name})
+        lengthInBits += ${helper.getEnumBaseTypeReference(enumField.type).sizeInBits};
+        <#break>
+    <#case "implicit">
+        <#assign implicitField = field.asImplicitField().orElseThrow()>
+        <#assign simpleTypeReference = implicitField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Implicit Field (${implicitField.name})
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#break>
+    <#case "manualArray">
+        <#assign manualArrayField = field.asManualArrayField().orElseThrow()>
+        <#assign arrayElementTypeReference = manualArrayField.type.asArrayTypeReference().orElseThrow().getElementTypeReference()>
+
+        // Manual Array Field (${manualArrayField.name})
+        lengthInBits += ${helper.toParseExpression(manualArrayField, helper.intTypeReference, manualArrayField.lengthExpression, parserArguments)} * 8;
+        <#break>
+    <#case "manual">
+        <#assign manualField = field.asManualField().orElseThrow()>
+
+        // Manual Field (${manualField.name})
+        lengthInBits += ${helper.toParseExpression(manualField, helper.intTypeReference, manualField.lengthExpression, parserArguments)};
+        <#break>
+    <#case "optional">
+        <#assign optionalField = field.asOptionalField().orElseThrow()>
+
+        // Optional Field (${optionalField.name})
+        if(${optionalField.name} != null) {
+        <#if optionalField.type.isSimpleTypeReference()>
+            <#assign simpleTypeReference = optionalField.type.asSimpleTypeReference().orElseThrow()>
+            <#if simpleTypeReference.isVstringTypeReference()>
+                <#assign vstringTypeReference = simpleTypeReference.asVstringTypeReference().orElseThrow()>
+            lengthInBits += ${helper.toSerializationExpression(optionalField, helper.intTypeReference, vstringTypeReference.getLengthExpression(), parserArguments)};
+            <#else>
+            lengthInBits += ${simpleTypeReference.sizeInBits};
+            </#if>
+        <#elseif helper.isEnumField(field)>
+            lengthInBits += ${helper.getEnumBaseTypeReference(optionalField.type).sizeInBits};
+        <#elseif optionalField.type.isDataIoTypeReference()>
+            lengthInBits += ${optionalField.type.asComplexTypeReference().orElseThrow().name}.getLengthInBits(${optionalField.name}<#if optionalField.type.asComplexTypeReference().orElseThrow().params?has_content>, <#list optionalField.type.asComplexTypeReference().orElseThrow().params.orElseThrow() as param>${helper.toParseExpression(optionalField, helper.anyTypeReference, param, parserArguments)}<#sep>, </#sep></#list></#if>);
+        <#else>
+            lengthInBits += ${optionalField.name}.getLengthInBits();
+        </#if>
+        }
+        <#break>
+    <#case "padding">
+        <#assign paddingField = field.asPaddingField().orElseThrow()>
+        <#assign simpleTypeReference = paddingField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Padding Field (padding)
+        <#-- We're replacing the "lastItem" with 'false' here as the item itself can't know if it is the last -->
+        int _timesPadding = (int) (${helper.toParseExpression(paddingField, helper.intTypeReference, paddingField.paddingCondition, parserArguments)});
+        while (_timesPadding-- > 0) {
+            lengthInBits += ${simpleTypeReference.sizeInBits};
+        }
+        <#break>
+    <#case "reserved">
+        <#assign reservedField = field.asReservedField().orElseThrow()>
+        <#assign simpleTypeReference = reservedField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Reserved Field (reserved)
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#break>
+    <#case "simple">
+        <#assign simpleField = field.asSimpleField().orElseThrow()>
+
+        // Simple field (${simpleField.name})
+        <#if simpleField.type.isSimpleTypeReference()>
+            <#assign simpleTypeReference = simpleField.type.asSimpleTypeReference().orElseThrow()>
+            <#if simpleTypeReference.isVstringTypeReference()>
+                <#assign vstringTypeReference = simpleTypeReference.asVstringTypeReference().orElseThrow()>
+        lengthInBits += ${helper.toSerializationExpression(simpleField, helper.intTypeReference, vstringTypeReference.getLengthExpression(), parserArguments)};
+            <#else>
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+            </#if>
+        <#elseif helper.isEnumField(field)>
+        lengthInBits += ${helper.getEnumBaseTypeReference(simpleField.type).sizeInBits};
+        <#elseif simpleField.type.isDataIoTypeReference()>
+        lengthInBits += ${simpleField.type.asComplexTypeReference().orElseThrow().name}.getLengthInBits(${simpleField.name}<#if simpleField.type.asComplexTypeReference().orElseThrow().params?has_content>, <#list simpleField.type.asComplexTypeReference().orElseThrow().params.orElseThrow() as param>${helper.toParseExpression(simpleField, helper.anyTypeReference, param, parserArguments)}<#sep>, </#sep></#list></#if>);
+        <#else>
+        lengthInBits += ${simpleField.name}.getLengthInBits();
+        </#if>
+        <#break>
+    <#case "switch">
+        <#assign switchField = field.asSwitchField().orElseThrow()>
+
+        // Length of sub-type elements will be added by sub-type...
+        <#break>
+    <#case "unknown">
+        <#assign unknownField = field.asUnknownField().orElseThrow()>
+        <#assign simpleTypeReference = unknownField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Unknown field
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#break>
+    <#case "virtual">
+        <#assign virtualField = field.asVirtualField().orElseThrow()>
+
+        // A virtual field doesn't have any in- or output.
+        <#break>
+</#switch>
+</#list>
+
+        return lengthInBits;
+    }
+
+<#-- The parse and serialize methods here are just proxies for forwardning the requests to static counterparts -->
+    <#if !type.isDiscriminatedChildTypeDefinition()>
+    public static ${type.name} staticParse(ReadBuffer readBuffer, Object... args) throws ParseException {
+        PositionAware positionAware = readBuffer;
+        <#if parserArguments?has_content>
+        if((args == null) || (args.length != ${parserArguments?size})) {
+            throw new PlcRuntimeException("Wrong number of arguments, expected ${parserArguments?size}, but got " + args.length);
+        }
+            <#list parserArguments as parserArgument>
+                <#assign languageName=helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)>
+        ${languageName} ${parserArgument.name};
+        if(args[${parserArgument?index}] instanceof ${languageName}) {
+            ${parserArgument.name} = (${languageName}) args[${parserArgument?index}];
+                <#if parserArgument.type.isSimpleTypeReference() || parserArgument.type.isEnumTypeReference()>
+        } else if (args[${parserArgument?index}] instanceof String) {
+            ${parserArgument.name} = ${languageName}.valueOf((String) args[${parserArgument?index}]);
+                </#if>
+        } else {
+            throw new PlcRuntimeException("Argument ${parserArgument?index} expected to be of type ${languageName} or a string which is parseable but was " + args[${parserArgument?index}].getClass().getName());
+        }
+            </#list>
+        </#if>
+        return staticParse(readBuffer<#if parserArguments?has_content>, <#list parserArguments as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>);
+    }
+
+    </#if>
+<#-- Here come the actual parse and serialize methods that actually do the parsing and serlaizing -->
+    <#assign hasParserArguments=parserArguments?has_content/>
+    <#assign parserArgumentList><#if hasParserArguments><#list parserArguments as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if></#assign>
+    public static ${type.name}<#if type.isDiscriminatedChildTypeDefinition()>Builder staticParseBuilder<#else> staticParse</#if>(ReadBuffer readBuffer<#if hasParserArguments>, ${parserArgumentList}</#if>) throws ParseException {
+        readBuffer.pullContext("${type.name}");
+        PositionAware positionAware = readBuffer;
+        int startPos = positionAware.getPos();
+        int curPos;
+    <#assign reservedFieldIndex=0>
+    <#list type.fields as field>
+        <#switch field.typeName>
+            <#case "array">
+                <#assign arrayField = field.asArrayField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+                <#assign arrayElementTypeReference = arrayField.type.asArrayTypeReference().orElseThrow().getElementTypeReference()>
+
+                <#if arrayElementTypeReference.isByteBased()>
+                    <#if !field.isCountArrayField() && !field.isLengthArrayField()>
+                        throw new ParseException("array fields of type byte only support 'count' and 'length' loop-types.");
+                    </#if>
+                    byte[] ${namedField.name} = readBuffer.readByteArray("${namedField.name}", Math.toIntExact(${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#else>
+                <#-- If this is a count array, we can directly initialize an array with the given size -->
+                    <#if field.isCountArrayField()>
+                        ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = readCountArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
+                    <#-- In all other cases do we have to work with a list, that is later converted to an array -->
+                    <#else>
+                    <#-- For a length array, we read data till the read position of the buffer reaches a given position -->
+                        <#if field.isLengthArrayField()>
+                            ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = readLengthArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
+                        <#-- A terminated array keeps on reading data as long as the termination expression evaluates to false -->
+                        <#elseif field.isTerminatedArrayField()>
+                            ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = readTerminatedArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, () -> ((boolean) (${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}))${helper.getFieldOptions(typedField, parserArguments)});
+                        </#if>
+                    </#if>
+                </#if>
+                <#break>
+            <#case "assert">
+                <#assign assertField = field.asAssertField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, (${helper.getLanguageTypeNameForField(field)}) (${helper.toParseExpression(assertField, assertField.type, assertField.conditionExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "checksum">
+                <#assign checksumField = field.asChecksumField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, (${helper.getLanguageTypeNameForField(field)}) (${helper.toParseExpression(checksumField, checksumField.type, checksumField.checksumExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "const">
+                <#assign constField = field.asConstField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, ${type.name}.${namedField.name?upper_case}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "discriminator">
+                <#assign discriminatorField = field.asDiscriminatorField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "enum">
+                <#assign enumField = field.asEnumField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", "${enumField.type.asNonSimpleTypeReference().orElseThrow().typeDefinition.name}", readEnum(${enumField.type.asNonSimpleTypeReference().orElseThrow().typeDefinition.name}::firstEnumForField${enumField.fieldName?cap_first}, ${helper.getDataReaderCall(helper.getEnumFieldTypeReference(enumField.type, enumField.fieldName))})${helper.getFieldOptions(typed [...]
+                <#break>
+            <#case "implicit">
+                <#assign implicitField = field.asImplicitField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "manualArray">
+                <#assign manualArrayField = field.asManualArrayField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+                <#assign arrayElementTypeReference = manualArrayField.type.asArrayTypeReference().orElseThrow().getElementTypeReference()>
+
+                <#if arrayElementTypeReference.isByteBased()>
+                    byte[] ${namedField.name} = readManualByteArrayField("${namedField.name}", readBuffer, (byte[] _values) -> (boolean) (${helper.toParseExpression(manualArrayField, helper.boolTypeReference, manualArrayField.loopExpression, parserArguments)}), () -> (byte) (${helper.toParseExpression(manualArrayField, manualArrayField.type, manualArrayField.parseExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#else>
+                    ${helper.getNonPrimitiveLanguageTypeNameForField(manualArrayField)} ${namedField.name} = readManualArrayField("${namedField.name}", readBuffer, (${helper.getNonPrimitiveLanguageTypeNameForField(manualArrayField)} _values) -> (boolean) (${helper.toParseExpression(manualArrayField, helper.boolTypeReference, manualArrayField.loopExpression, parserArguments)}), () -> (${helper.getLanguageTypeNameForTypeReference(manualArrayField.type.elementTypeReference)}) (${helper.toPa [...]
+                </#if>
+                <#break>
+            <#case "manual">
+                <#assign manualField = field.asManualField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${manualField.name} = readManualField("${namedField.name}", readBuffer, () -> (${helper.getLanguageTypeNameForField(manualField)}) (${helper.toParseExpression(manualField, manualField.type, manualField.parseExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "optional">
+                <#assign optionalField = field.asOptionalField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}<#if optionalField.conditionExpression.present>, ${helper.toParseExpression(optionalField, helper.boolTypeReference, optionalField.conditionExpression.get(), parserArguments)}</#if>${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "padding">
+                <#assign paddingField = field.asPaddingField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign simpleTypeReference = paddingField.type.asSimpleTypeReference().orElseThrow()>
+
+                read${field.typeName?cap_first}Field(${helper.getDataReaderCall(typedField.type)}, (int) (${helper.toParseExpression(paddingField, paddingField.type, paddingField.paddingCondition, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "reserved">
+                <#assign reservedField = field.asReservedField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForTypeReference(reservedField.type, false)} reservedField${reservedFieldIndex}<#assign reservedFieldIndex=reservedFieldIndex+1> = read${field.typeName?cap_first}Field("reserved", ${helper.getDataReaderCall(typedField.type)}, ${helper.getReservedValue(reservedField)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "simple">
+                <#assign simpleField = field.asSimpleField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = <#if typedField.type.isEnumTypeReference()>readEnumField("${namedField.name}", "${helper.getLanguageTypeNameForField(field)}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});<#else>read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});</#if>
+                <#break>
+            <#case "switch">
+                <#assign switchField = field.asSwitchField().orElseThrow()>
+
+                // Switch Field (Depending on the discriminator values, passes the instantiation to a sub-type)
+                ${type.name}Builder builder = null;
+                <#list switchField.cases as case>
+                    <@compress single_line=true>
+                        <#if case.discriminatorValueTerms?has_content>
+                            if(
+                            <#list case.discriminatorValueTerms as discriminatorValueTerm>
+                                <#if helper.isWildcard(discriminatorValueTerm)>
+                                    true
+                                <#else>
+                                    <#assign discriminatorExpression=switchField.discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
+                                    <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.discriminatorName]>
+                                    EvaluationHelper.equals(
+                                    ${helper.toParseExpression(switchField, discriminatorType, discriminatorExpression, parserArguments)},
+                                    <#if discriminatorType.isEnumTypeReference()>
+                                        ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(switchField, discriminatorType, discriminatorValueTerm, parserArguments)}
+                                    <#else>
+                                        (${helper.getLanguageTypeNameForTypeReference(discriminatorType, true)}) ${helper.toParseExpression(switchField, discriminatorType, discriminatorValueTerm, parserArguments)}
+                                    </#if>
+                                    )
+                                </#if>
+                                <#sep> && </#sep>
+                            </#list>
+                            )
+                        </#if>{
+                    </...@compress>
+                    <@compress single_line=true>
+                        <#assign hasCaseParseArguments=case.allParserArguments.isPresent() && case.allParserArguments.orElseThrow()?has_content>
+                        <#assign caseParseArguments><#if hasCaseParseArguments><#list case.allParserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if></#assign>
+                        builder = ${case.name}.staticParseBuilder(readBuffer<#if hasCaseParseArguments>, ${tracer.dive("case parse arguments")} ${caseParseArguments}</#if>);
+                    </...@compress>
+                    }<#sep> else </#sep>
+                </#list>
+                if (builder == null) {
+                    throw new ParseException("Unsupported case for discriminated type"<#if switchField.getDiscriminatorExpressions()?has_content>+" parameters ["<#list switchField.getDiscriminatorExpressions() as discriminatorExpression>+"${discriminatorExpression.stringRepresentation()}="+${helper.toParseExpression(null, null, discriminatorExpression, parserArguments)}<#sep>+" "</#sep></#list>+"]"</#if>);
+                }
+                <#break>
+            <#case "unknown">
+                <#assign unknownField = field.asUnknownField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+
+                read${field.typeName?cap_first}Field("unknown", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "virtual">
+                <#assign virtualField = field.asVirtualField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getLanguageTypeNameForField(field)}.class, ${helper.toParseExpression(virtualField, virtualField.type, virtualField.valueExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "validation">
+                <#assign validationField = field.asValidationField().orElseThrow()>
+                // Validation
+                if (!(${helper.toParseExpression(validationField, helper.boolTypeReference, validationField.getValidationExpression(), null)})) {
+                    <#assign errorType="ParseValidationException">
+                    <#if !validationField.shouldFail()><#assign errorType="ParseAssertException"></#if>
+                    throw new ${errorType}(${validationField.getDescription().orElse("\"Validation failed\"")});
+                }
+                <#break>
+            <#case "peek">
+                <#assign peekField = field.asPeekField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}<#if peekField.offsetExpression.present>, ${helper.toParseExpression(peekField, helper.boolTypeReference, peekField.offsetExpression.get(), parserArguments)}</#if>${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+        </#switch>
+    </#list>
+
+    readBuffer.closeContext("${type.name}");
+    // Create the instance
+    <#if type.isDiscriminatedChildTypeDefinition()>
+        return new ${type.name}Builder(
+        <#list type.propertyFields as field>
+            ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        <#if (type.propertyFields?has_content || filteredParentParserArguments?has_content) && reservedFields?has_content>,</#if>
+        <#list reservedFields as reservedField>
+            reservedField${reservedField?index}<#sep>, </#sep>
+        </#list>
+        );
+    <#elseif type.isDiscriminatedParentTypeDefinition()>
+        ${type.name} _${type.name?uncap_first} = builder.build(
+        <#list type.propertyFields as field>
+            ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        );
+        <#list reservedFields as reservedField>
+        _${type.name?uncap_first}.reservedField${reservedField?index} = reservedField${reservedField?index};
+        </#list>
+        return _${type.name?uncap_first};
+    <#else>
+        ${type.name} _${type.name?uncap_first};
+        _${type.name?uncap_first} = new ${type.name}(
+        <#list type.propertyFields as field>
+            ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        );
+        <#list reservedFields as reservedField>
+            _${type.name?uncap_first}.reservedField${reservedField?index} = reservedField${reservedField?index};
+        </#list>
+        return _${type.name?uncap_first};
+    </#if>
+    }
+
+    <#if type.isDiscriminatedParentTypeDefinition()>
+        public static interface ${type.name}Builder {
+            ${type.name} build(
+        <#list type.propertyFields as field>
+            ${helper.getLanguageTypeNameForField(field)} ${field.name}<#sep>, </#sep>
+            </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${helper.getLanguageTypeNameForTypeReference(arg.type)} ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        );
+        }
+
+    </#if>
+    <#if type.isDiscriminatedChildTypeDefinition()>
+        public static class ${type.name}Builder implements ${type.parentType.orElseThrow().name}.${type.parentType.orElseThrow().name}Builder {
+        <#if type.propertyFields?has_content>
+            <#list type.propertyFields as field>
+        private final ${helper.getLanguageTypeNameForField(field)} ${field.name};
+            </#list>
+        </#if>
+        <#if filteredParserArguments?has_content>
+            <#list filteredParserArguments as arg>
+        private final ${helper.getLanguageTypeNameForTypeReference(arg.type)} ${arg.name};
+            </#list>
+        </#if>
+        <#list reservedFields as reservedField>
+        private final ${helper.getLanguageTypeNameForTypeReference(reservedField.type, false)} reservedField${reservedField?index};
+        </#list>
+
+        public ${type.name}Builder(
+        <#list type.propertyFields as field>
+            ${helper.getLanguageTypeNameForField(field)} ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${helper.getLanguageTypeNameForTypeReference(arg.type)} ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        <#if (type.propertyFields?has_content || filteredParentParserArguments?has_content) && reservedFields?has_content>,</#if>
+        <#list reservedFields as reservedField>
+            ${helper.getLanguageTypeNameForTypeReference(reservedField.type, false)} reservedField${reservedField?index}<#sep>, </#sep>
+        </#list>
+        ) {
+        <#list type.propertyFields as field>
+            this.${field.name} = ${field.name};
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#list filteredParserArguments as arg>
+            this.${arg.name} = ${arg.name};
+            </#list>
+        </#if>
+        <#list reservedFields as reservedField>
+            this.reservedField${reservedField?index} = reservedField${reservedField?index};
+        </#list>
+        }
+
+        public ${type.name} build(
+        <#list type.parentType.orElseThrow().asComplexTypeDefinition().orElseThrow().propertyFields as field>
+            ${helper.getLanguageTypeNameForField(field)} ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParentParserArguments?has_content>
+            <#if type.parentType.orElseThrow().asComplexTypeDefinition().orElseThrow().propertyFields?has_content>, </#if>
+                <#list filteredParentParserArguments as arg>
+                ${helper.getLanguageTypeNameForTypeReference(arg.type)} ${arg.name}<#sep>, </#sep>
+                </#list>
+            </#if>
+        ) {
+            ${type.name} ${type.name?uncap_first} = new ${type.name}(
+            <#list type.allPropertyFields as field>
+                ${field.name}<#sep>, </#sep>
+            </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.allPropertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>);
+        <#list reservedFields as reservedField>
+            ${type.name?uncap_first}.reservedField${reservedField?index} = reservedField${reservedField?index};
+        </#list>
+            return ${type.name?uncap_first};
+        }
+    }
+
+    </#if>
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ${type.name})) {
+            return false;
+        }
+        ${type.name} that = (${type.name}) o;
+        return
+            <#if type.propertyFields?has_content>
+            <#list type.propertyFields as field>
+            (get${field.name?cap_first}() == that.get${field.name?cap_first}()) &&
+            </#list>
+            </#if>
+            <#if type.parentType.isPresent()>
+            super.equals(that) &&
+            </#if>
+            true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+            <#if type.parentType.isPresent()>
+            super.hashCode()<#if type.propertyFields?has_content>,</#if>
+            </#if>
+            <#if type.propertyFields?has_content>
+            <#list type.propertyFields as field>
+            get${field.name?cap_first}()<#sep>,</#sep>
+            </#list>
+            </#if>
+        );
+    }
+
+    @Override
+    public String toString() {
+        WriteBufferBoxBased writeBufferBoxBased = new WriteBufferBoxBased(true, true);
+        try {
+            writeBufferBoxBased.writeSerializable(this);
+        } catch (SerializationException e) {
+            throw new RuntimeException(e);
+        }
+        return "\n" + writeBufferBoxBased.getBox().toString()+ "\n";
+    }
+}
+</#outputformat>
\ No newline at end of file
diff --git a/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh
new file mode 100644
index 0000000000..7779e6e266
--- /dev/null
+++ b/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh
@@ -0,0 +1,522 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<#-- Prevent freemarker from escaping stuff -->
+<#outputformat "undefined">
+<#-- Declare the name and type of variables passed in to the template -->
+<#-- @ftlvariable name="languageName" type="java.lang.String" -->
+<#-- @ftlvariable name="protocolName" type="java.lang.String" -->
+<#-- @ftlvariable name="outputFlavor" type="java.lang.String" -->
+<#-- @ftlvariable name="helper" type="org.apache.plc4x.language.java.JavaLanguageTemplateHelper" -->
+<#-- @ftlvariable name="tracer" type="org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer" -->
+<#-- @ftlvariable name="type" type="org.apache.plc4x.plugins.codegenerator.types.definitions.ComplexTypeDefinition" -->
+${helper.packageName(protocolName, languageName, outputFlavor)?replace(".", "/")}/${type.name}.java
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package ${helper.packageName(protocolName, languageName, outputFlavor)};
+
+import static org.apache.plc4x.java.spi.generation.StaticHelper.*;
+
+import org.apache.plc4x.java.api.model.PlcTag;
+import org.apache.plc4x.java.api.value.*;
+import org.apache.plc4x.java.spi.generation.EvaluationHelper;
+import org.apache.plc4x.java.spi.generation.ParseException;
+import org.apache.plc4x.java.spi.generation.SerializationException;
+import org.apache.plc4x.java.spi.generation.ReadBuffer;
+import org.apache.plc4x.java.spi.generation.WriteBuffer;
+import org.apache.plc4x.java.spi.generation.ByteOrder;
+import ${helper.packageName(protocolName, languageName, outputFlavor)}.*;
+import org.apache.plc4x.java.spi.values.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.time.*;
+import java.util.*;
+import java.util.function.Supplier;
+
+// Code generated by code-generation. DO NOT EDIT.
+
+<#-- TODO: the code below implies that parserArguments will be null if not present... not pretty  -->
+<#if type.parserArguments.isPresent()><#assign parserArguments=type.parserArguments.orElseThrow()></#if>
+public class ${type.name} {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(${type.name}.class);
+    public static PlcValue staticParse(ReadBuffer readBuffer<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) throws ParseException {
+        <#assign defaultCaseOutput=false>
+        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
+        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
+            <@compress single_line=true>
+                <#if case.discriminatorValueTerms?has_content>
+                    if(
+                    <#list case.discriminatorValueTerms as discriminatorValueTerm>
+                        <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
+                        <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
+                        EvaluationHelper.equals(
+                        ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
+                        <#if discriminatorType.isEnumTypeReference()>
+                        ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                        <#else>
+                        ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                        </#if>
+                    )
+                        <#sep> && </#sep>
+                    </#list>
+                    )
+                <#else>
+                    <#assign defaultCaseOutput=true>
+                </#if>
+            </...@compress> { // ${case.name}
+            <#assign valueDefined=false>
+            <#list case.fields as field>
+                <#switch field.typeName>
+                    <#case "array">
+                        <#assign arrayField = field.asArrayField().orElseThrow()>
+                        <#assign elementTypeReference=arrayField.type.elementTypeReference>
+            // Array field (${arrayField.name})
+            <#-- Only update curPos if the length expression uses it -->
+                        <#if arrayField.loopExpression.contains("curPos")>
+            curPos = readBuffer.getPos() - startPos;
+                        </#if>
+            <#-- If this is a count array, we can directly initialize an array with the given size -->
+                        <#if field.isCountArrayField()>
+            // Count array
+            if(${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)} > Integer.MAX_VALUE) {
+                throw new ParseException("Array count of " + (${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)}) + " exceeds the maximum allowed count of " + Integer.MAX_VALUE);
+            }
+            List<PlcValue> ${arrayField.name};
+            {
+                int itemCount = (int) ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)};
+                ${arrayField.name} = new LinkedList<>();
+                for(int curItem = 0; curItem < itemCount; curItem++) {
+                    ${arrayField.name}.add(new ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}((${helper.getLanguageTypeNameForTypeReference(elementTypeReference, false)}) <#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)})<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if elementTypeReference.params.isPresen [...]
+                }
+            }
+            <#-- In all other cases do we have to work with a list, that is later converted to an array -->
+                        <#else>
+            <#-- For a length array, we read data till the read position of the buffer reaches a given position -->
+                            <#if arrayField.isLengthArrayField()>
+            // Length array
+            long _${arrayField.name}Length = ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)};
+            long ${arrayField.name}EndPos = readBuffer.getPos() + _${arrayField.name}Length;
+            List<PlcValue> value = new LinkedList<>();
+            while(readBuffer.getPos() < ${arrayField.name}EndPos) {
+                value.add(
+                <#if elementTypeReference.isSimpleTypeReference()>
+                    new ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}(${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)})
+                <#else>${elementTypeReference.asNonSimpleTypeReference().orElseThrow().name}IO.staticParse(readBuffer
+                    <#if elementTypeReference.params.isPresent()>,
+                        <#list elementTypeReference.params.orElseThrow() as parserArgument>
+                            (${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index), true)}) (${helper.toParseExpression(arrayField, elementTypeReference, parserArgument,parserArguments)})
+                            <#sep>, </#sep>
+                        </#list>
+                    </#if>
+                    )
+                </#if>
+                );
+            }
+            <#-- A terminated array keeps on reading data as long as the termination expression evaluates to false -->
+                            <#elseif arrayField.isTerminatedArrayField()>
+            // Terminated array
+            ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = new LinkedList<>();
+            while(!((boolean) (${helper.toParseExpression(arrayField, helper.boolTypeReference, arrayField.loopExpression,parserArguments)}))) {
+                ${arrayField.name}.add(<#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if arrayField.params.isPresent()>, <#list arrayField.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parser [...]
+
+                <#-- After parsing, update the current position, but only if it's needed -->
+                                <#if arrayField.loopExpression.contains("curPos")>
+                curPos = readBuffer.getPos() - startPos;
+                                </#if>
+            }
+                            </#if>
+                        </#if>
+                        <#if arrayField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                    <#case "const">
+                        <#assign constField=field.asConstField().orElseThrow()>
+
+            // Const Field (${constField.name})
+            ${helper.getNonPrimitiveLanguageTypeNameForField(constField)} ${constField.name} = ${helper.getReadBufferReadMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), "", constField)};
+            if(${constField.name} != ${dataIoTypeDefinition.name}.${constField.name?upper_case}) {
+                throw new ParseException("Expected constant value " + ${dataIoTypeDefinition.name}.${constField.name?upper_case} + " but got " + ${constField.name});
+            }
+                        <#if constField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                    <#case "enum">
+                        <#assign enumField=field.asEnumField().orElseThrow()>
+
+            // Enum field (${enumField.name})
+            ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)} ${enumField.name} = ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)}.enumForValue(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(enumField.type.asSimpleTypeReference().orElseThrow()), "", enumField)});
+                        <#if enumField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                    <#case "manual">
+                        <#assign manualField=field.asManualField().orElseThrow()>
+
+            // Manual Field (${manualField.name})
+            ${helper.getLanguageTypeNameForField(field)} ${manualField.name} = (${helper.getLanguageTypeNameForField(manualField)}) (${helper.toParseExpression(manualField, manualField.type, manualField.parseExpression,parserArguments)});
+                        <#if manualField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                    <#case "reserved">
+                        <#assign reservedField=field.asReservedField().orElseThrow()>
+
+            // Reserved Field (Compartmentalized so the "reserved" variable can't leak)
+            {
+                ${helper.getLanguageTypeNameForField(field)} reserved = ${helper.getReadBufferReadMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), "", reservedField)};
+                if(reserved != ${helper.getReservedValue(reservedField)}) {
+                    LOGGER.info("Expected constant value " + ${reservedField.referenceValue} + " but got " + reserved + " for reserved field.");
+                }
+            }
+                    <#break>
+                    <#case "simple">
+                        <#assign simpleField=field.asSimpleField().orElseThrow()>
+
+                        <#if helper.isEnumField(simpleField)>
+            // Enum field (${simpleField.name})
+            ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} ${simpleField.name} = ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)}.enumForValue(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(simpleField.type), "", simpleField)});
+                        <#else>
+            // Simple Field (${simpleField.name})
+            ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} ${simpleField.name} = <#if simpleField.type.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "", simpleField)}<#else>${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if simpleField.params.isPresent()>, <#list field.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.g [...]
+                        </#if>
+                        <#if case.name == "Struct" ||
+                            ((case.name == "DATE_AND_TIME") && ((simpleField.name == "year") || (simpleField.name == "month") || (simpleField.name == "day") || (simpleField.name == "hour") || (simpleField.name == "minutes") || (simpleField.name == "seconds"))) ||
+                            ((case.name == "DATE_AND_TIME") && (simpleField.name == "secondsSinceEpoch")) ||
+                            ((case.name == "DATE") && ((simpleField.name == "year") || (simpleField.name == "month") || (simpleField.name == "day"))) ||
+                            ((case.name == "TIME_OF_DAY") && ((simpleField.name == "hour") || (simpleField.name == "minutes") || (simpleField.name == "seconds"))) ||
+                        simpleField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                </#switch>
+            </#list>
+            <#if case.name == "Struct">
+
+                <#-- In this case we need to wrap each field in a PlcValue that matches it's natural type -->
+            Map<String, PlcValue> _map = new HashMap<>();
+                <#list case.fields as field>
+                    <#if field.isArrayField()>
+                        <#assign field=field.asArrayField().orElseThrow()>
+            _map.put("${field.name}", new PlcList(${field.name}));
+                    <#elseif field.isPropertyField()>
+                        <#assign field=field.asPropertyField().orElseThrow()>
+                        <#switch helper.getLanguageTypeNameForTypeReference(field.type)>
+                            <#case "Boolean">
+            _map.put("${field.name}", new PlcBOOL(${field.name}));
+                                <#break>
+                            <#case "Byte">
+            _map.put("${field.name}", new PlcSINT(${field.name}));
+                                <#break>
+                            <#case "Short">
+            _map.put("${field.name}", new PlcINT(${field.name}));
+                                <#break>
+                            <#case "Integer">
+            _map.put("${field.name}", new PlcDINT(${field.name}));
+                                <#break>
+                            <#case "Long">
+            _map.put("${field.name}", new PlcLINT(${field.name}));
+                                <#break>
+                            <#case "BigInteger">
+            _map.put("${field.name}", new PlcBigInteger(${field.name}));
+                                <#break>
+                            <#case "Float">
+            _map.put("${field.name}", new PlcREAL(${field.name}));
+                                <#break>
+                            <#case "Double">
+            _map.put("${field.name}", new PlcLREAL(${field.name}));
+                                <#break>
+                            <#case "BigDecimal">
+            _map.put("${field.name}", new PlcBigDecimal(${field.name}));
+                                <#break>
+                            <#case "String">
+            _map.put("${field.name}", new PlcSTRING(${field.name}));
+                                <#break>
+                            <#case "LocalTime">
+            _map.put("${field.name}", new PlcTIME_OF_DAY(${field.name}));
+                                <#break>
+                            <#case "LocalDate">
+            _map.put("${field.name}", new PlcDATE(${field.name}));
+                                <#break>
+                            <#case "LocalDateTime">
+            _map.put("${field.name}", new PlcDATE_AND_TIME(${field.name}));
+                                <#break>
+                        </#switch>
+                    </#if>
+                </#list>
+                <#assign valueDefined=true>
+            </#if>
+
+            <#if valueDefined>
+                <#switch case.name>
+                    <#case "TIME">
+            return new PlcTIME(value);
+                    <#break>
+                    <#case "DATE">
+                    <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day")>
+            LocalDate value = LocalDate.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue());
+                    </#if>
+            return new PlcDATE(value);
+                    <#break>
+                    <#case "TIME_OF_DAY">
+                    <#if helper.hasFieldsWithNames(case.fields, "hour", "minutes", "seconds", "nanos")>
+            LocalTime value = LocalTime.of(hour.intValue(), minutes.intValue(), seconds.intValue(), nanos.intValue());
+                    <#elseif helper.hasFieldsWithNames(case.fields, "hour", "minutes", "seconds")>
+            LocalTime value = LocalTime.of(hour.intValue(), minutes.intValue(), seconds.intValue());
+                    </#if>
+            return new PlcTIME_OF_DAY(value);
+                    <#break>
+                    <#case "DATE_AND_TIME">
+                    <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds", "nanos")>
+            LocalDateTime value = LocalDateTime.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue(), hour.intValue(), minutes.intValue(), seconds.intValue(), nanos.intValue());
+                    <#elseif helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds")>
+            LocalDateTime value = LocalDateTime.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue(), hour.intValue(), minutes.intValue(), seconds.intValue());
+                    <#elseif helper.hasFieldsWithNames(case.fields, "secondsSinceEpoch")>
+            LocalDateTime value = LocalDateTime.ofEpochSecond(secondsSinceEpoch, 0, ZoneOffset.UTC);
+                    </#if>
+            return new PlcDATE_AND_TIME(value);
+                    <#break>
+                    <#case "Struct">
+            return new PlcStruct(_map);
+                    <#break>
+                    <#case "List">
+            return new PlcList(value);
+                    <#break>
+                    <#default>
+            return new Plc${case.name}(value);
+                </#switch>
+            </#if>
+        }<#sep> else </#sep></#list>
+        <#if !defaultCaseOutput>
+        return null;
+        </#if>
+    }
+
+<#if outputFlavor != "passive">
+    public static void staticSerialize(WriteBuffer writeBuffer, PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) throws SerializationException {
+        staticSerialize(writeBuffer, _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>, ByteOrder.BIG_ENDIAN);
+    }
+
+    public static void staticSerialize(WriteBuffer writeBuffer, PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>, ByteOrder byteOrder) throws SerializationException {
+        <#assign defaultCaseOutput=false>
+        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
+        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
+        <@compress single_line=true>
+            <#if case.discriminatorValueTerms?has_content>
+                if(
+                <#list case.discriminatorValueTerms as discriminatorValueTerm>
+                    <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
+                    <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
+                    EvaluationHelper.equals(
+                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
+                    <#if discriminatorType.isEnumTypeReference()>
+                    ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                    <#else>
+                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                    </#if>
+                    )
+                    <#sep> && </#sep>
+                </#list>
+                )
+            <#else>
+                <#assign defaultCaseOutput=true>
+            </#if>
+        </...@compress> { // ${case.name}
+            <#list case.fields as field>
+                <#switch field.typeName>
+                    <#case "array">
+                        <#assign arrayField=field.asArrayField().orElseThrow()>
+                        <#assign elementTypeReference=arrayField.type.elementTypeReference>
+            PlcList values = (PlcList) _value;
+
+                        <#if case.name == "Struct">
+            for (PlcValue val : ((List<PlcValue>) values.getStruct().get("${arrayField.name}").getList())) {
+                <#if elementTypeReference.isByteBased()>
+                ${helper.getLanguageTypeNameForField(arrayField)} value = (${helper.getLanguageTypeNameForField(arrayField)}) val.getRaw();
+                writeBuffer.writeByteArray("", value);
+                <#else>
+                ${helper.getLanguageTypeNameForField(arrayField)} value = (${helper.getLanguageTypeNameForField(arrayField)}) val.get${helper.getLanguageTypeNameForField(arrayField)?cap_first}();
+                ${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "value", arrayField)};
+                </#if>
+            }
+                        <#else>
+            for (PlcValue val : ((List<PlcValue>) values.getList())) {
+                <#if elementTypeReference.isByteBased()>
+                byte[] value = (byte[]) val.getRaw();
+                writeBuffer.writeByteArray("", value);
+                <#else>
+                ${helper.getLanguageTypeNameForTypeReference(elementTypeReference)} value = (${helper.getLanguageTypeNameForTypeReference(elementTypeReference)}) val.get${helper.getLanguageTypeNameForTypeReference(elementTypeReference)?cap_first}();
+                ${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "(" + arrayField.name + ")", arrayField)};
+                </#if>
+            }
+                        </#if>
+
+                    <#if case.name == "BOOL">
+            while (writeBuffer.getPos() < writeBuffer.getData().length) {
+                writeBuffer.writeBit(false);
+            }
+                    </#if>
+                    <#break>
+                    <#case "const">
+                        <#assign constField=field.asConstField().orElseThrow()>
+            // Const Field (${constField.name})
+            ${helper.getWriteBufferWriteMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), constField.referenceValue, constField)};
+                    <#break>
+                    <#case "enum">
+                        <#assign enumField=field.asEnumField().orElseThrow()>
+            // Enum field (${enumField.name})
+            ${helper.getLanguageTypeNameForField(field)} ${enumField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${enumField.name?cap_first}();
+            ${helper.getWriteBufferWriteMethodCall(helper.getEnumBaseTypeReference(field.asTypedField().orElseThrow().type), "(" + enumField.name + ".getValue())", enumField)};
+                    <#break>
+                    <#case "manual">
+                        <#assign manualField=field.asManualField().orElseThrow()>
+            // Manual Field (${manualField.name})
+            ${helper.toSerializationExpression(manualField, manualField.type, manualField.serializeExpression, type.parserArguments.orElse(null))};
+                    <#break>
+                    <#case "reserved">
+                        <#assign reservedField=field.asReservedField().orElseThrow()>
+            // Reserved Field
+            ${helper.getWriteBufferWriteMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), helper.getReservedValue(reservedField), reservedField)};
+                    <#break>
+                    <#case "simple">
+                        <#assign simpleField=field.asSimpleField().orElseThrow()>
+            // Simple Field (${simpleField.name})
+                        <#if case.name == "Struct">
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.getStruct().get("${simpleField.name}").get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
+                        <#else>
+                            <#if simpleField.name == "value">
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
+                            <#elseif simpleField.name == "secondsSinceEpoch">
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
+                            <#else>
+                                <#-- Just for now -->
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = ${helper.getNullValueForTypeReference(simpleField.type)};
+                            </#if>
+                        </#if>
+                        <#if simpleField.type.isSimpleTypeReference()>
+            ${helper.getWriteBufferWriteMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "(" + simpleField.name + ")", simpleField)};
+                        <#else>
+            ${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.staticSerialize(writeBuffer, ${simpleField.name});
+                        </#if>
+                    <#break>
+                </#switch>
+            </#list>
+        }<#sep> else </#sep></#list>
+    }
+</#if>
+
+    public static int getLengthInBytes(PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) {
+        return (int) Math.ceil((float) getLengthInBits(_value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>) / 8.0);
+    }
+
+    public static int getLengthInBits(PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) {
+        int sizeInBits = 0;
+        <#assign defaultCaseOutput=false>
+        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
+        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
+        <@compress single_line=true>
+        <#if case.discriminatorValueTerms?has_content>
+        if(
+            <#list case.discriminatorValueTerms as discriminatorValueTerm>
+            <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
+            <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
+                EvaluationHelper.equals(
+                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
+                    <#if discriminatorType.isEnumTypeReference()>
+                    ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                    <#else>
+                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                    </#if>
+                )
+            <#sep> && </#sep>
+            </#list>
+        )
+        <#else>
+            <#assign defaultCaseOutput=true>
+        </#if>
+        </...@compress> { // ${case.name}
+            <#list case.fields as field>
+            <#switch field.typeName>
+            <#case "array">
+                <#assign arrayField=field.asArrayField().orElseThrow()>
+                <#assign elementTypeReference=arrayField.type.elementTypeReference>
+                PlcList values = (PlcList) _value;
+                <#if case.name == "Struct">
+                // TODO: Finish this!
+                <#elseif elementTypeReference.isComplexTypeReference()>
+                // TODO: Finish this!
+                <#else>
+                sizeInBits += values.getList().size() * ${elementTypeReference.asSimpleTypeReference().orElseThrow().sizeInBits};
+                </#if>
+                <#break>
+            <#case "const">
+                <#assign constField=field.asConstField().orElseThrow()>
+                // Const Field (${constField.name})
+                <#-- const fields are only simple type -->
+                sizeInBits += ${constField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
+                <#break>
+            <#case "enum">
+                <#assign enumField=field.asEnumField().orElseThrow()>
+                // Enum field (${enumField.name})
+                sizeInBits += ${helper.getEnumFieldSimpleTypeReference(enumField.type, enumField.fieldName).sizeInBits};
+                <#break>
+            <#case "manual">
+                <#assign manualField=field.asManualField().orElseThrow()>
+                // Manual Field (${manualField.name})
+                sizeInBits += ${helper.toSerializationExpression(manualField, helper.intTypeReference, manualField.lengthExpression, type.parserArguments.orElse(null))};
+                <#break>
+            <#case "reserved">
+                <#assign reservedField=field.asReservedField().orElseThrow()>
+                // Reserved Field
+                sizeInBits += ${reservedField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
+                <#break>
+            <#case "simple">
+                <#assign simpleField=field.asSimpleField().orElseThrow()>
+                // Simple Field (${simpleField.name})
+                sizeInBits += ${simpleField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
+                <#break>
+            </#switch>
+            </#list>
+        }<#sep> else </#sep></#list>
+        return sizeInBits;
+    }
+
+}
+</#outputformat>
diff --git a/code-generation/language-python/src/main/resources/templates/python/enum-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/enum-template.python.ftlh
new file mode 100644
index 0000000000..24da7f185b
--- /dev/null
+++ b/code-generation/language-python/src/main/resources/templates/python/enum-template.python.ftlh
@@ -0,0 +1,164 @@
+<#--
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<#-- Prevent freemarker from escaping stuff -->
+<#outputformat "undefined">
+<#-- Declare the name and type of variables passed in to the template -->
+<#-- @ftlvariable name="languageName" type="java.lang.String" -->
+<#-- @ftlvariable name="protocolName" type="java.lang.String" -->
+<#-- @ftlvariable name="outputFlavor" type="java.lang.String" -->
+<#-- @ftlvariable name="helper" type="org.apache.plc4x.language.java.JavaLanguageTemplateHelper" -->
+<#-- @ftlvariable name="tracer" type="org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer" -->
+<#-- @ftlvariable name="type" type="org.apache.plc4x.plugins.codegenerator.types.definitions.EnumTypeDefinition" -->
+${helper.packageName(protocolName, languageName, outputFlavor)?replace(".", "/")}/${type.name}.java
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package ${helper.packageName(protocolName, languageName, outputFlavor)};
+
+import org.apache.plc4x.java.spi.generation.Message;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// Code generated by code-generation. DO NOT EDIT.
+
+public enum ${type.name} {
+
+<@compress single_line=true>
+    <#list type.enumValues as enumValue>
+        ${enumValue.name}(
+            <#if type.type.isPresent()>
+                (${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)})
+                <#if type.type.orElseThrow().isNonSimpleTypeReference()>
+                    <#if type.type.orElseThrow().isEnumTypeReference()>
+                        ${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)}.${enumValue.value}
+                    <#else>
+                        ${enumValue.value}
+                    </#if>
+                <#else>
+                    ${enumValue.value}<#if helper.needsLongMarker(type.type)>L</#if>
+                </#if>
+            </#if>
+            <#if type.constantNames?has_content>
+                <#if type.type?has_content>, </#if>
+                <#list type.constantNames as constantName>
+                    <#if type.getConstantType(constantName).isNonSimpleTypeReference()>
+                        <#if helper.escapeValue(type.getConstantType(constantName), enumValue.getConstant(constantName).orElse(null)) == 'null'>
+                            null
+                        <#elseif type.getConstantType(constantName).isEnumTypeReference()>
+                            ${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)}.${helper.escapeValue(type.getConstantType(constantName), enumValue.getConstant(constantName).orElseThrow())}<#else>(${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)}) ${helper.escapeValue(type.getConstantType(constantName), enumValue.getConstant(constantName).orElseThrow())}</#if><#else>(${helper.getLanguageTypeNameForTypeReference(type [...]
+                        </#if>
+                    <#sep>, </#sep>
+                </#list>
+            </#if>)
+        <#sep>, </#sep>
+    </#list>;
+</...@compress>
+
+<#if type.type.isPresent()>
+    private static final Map<${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), false)}, ${type.name}> map;
+    static {
+        map = new HashMap<>();
+        for (${type.name} value : ${type.name}.values()) {
+            map.put((${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)}) value.getValue(), value);
+        }
+    }
+
+    private ${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} value;
+</#if>
+<#if type.constantNames?has_content>
+    <#list type.constantNames as constantName>
+        private ${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} ${constantName};
+    </#list>
+</#if>
+
+    ${type.name}(<#if type.type.isPresent()>${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} value</#if><#if type.constantNames?has_content><#if type.type?has_content>, </#if><#list type.constantNames as constantName>${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} ${constantName}<#sep>, </#sep></#list></#if>) {
+<#if type.type.isPresent()>        this.value = value;</#if>
+<#if type.constantNames?has_content>
+    <#list type.constantNames as constantName>
+        this.${constantName} = ${constantName};
+    </#list>
+</#if>
+    }
+
+<#if type.type.isPresent()>
+    public ${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} getValue() {
+        return value;
+    }
+</#if>
+
+<#if type.constantNames?has_content>
+    <#list type.constantNames as constantName>
+    public ${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} get${constantName?cap_first}() {
+        return ${constantName};
+    }
+
+    public static ${type.name} firstEnumForField${constantName?cap_first}(${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} fieldValue) {
+        for (${type.name} _val : ${type.name}.values()) {
+            if(_val.get${constantName?cap_first}() == fieldValue) {
+                return _val;
+            }
+        }
+        return null;
+    }
+
+    public static List<${type.name}> enumsForField${constantName?cap_first}(${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} fieldValue) {
+        List<${type.name}> _values = new ArrayList();
+        for (${type.name} _val : ${type.name}.values()) {
+            if(_val.get${constantName?cap_first}() == fieldValue) {
+                _values.add(_val);
+            }
+        }
+        return _values;
+    }
+
+    </#list>
+</#if>
+<#if type.type.isPresent()>
+    public static ${type.name} enumForValue(${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} value) {
+        return map.get(value);
+    }
+</#if>
+
+<#if type.type.isPresent()>
+    public static Boolean isDefined(${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} value) {
+        return map.containsKey(value);
+    }
+</#if>
+
+}
+</#outputformat>
diff --git a/code-generation/language-python/src/test/resources/integration-test/pom.xml b/code-generation/language-python/src/test/resources/integration-test/pom.xml
new file mode 100644
index 0000000000..4757bda6a1
--- /dev/null
+++ b/code-generation/language-python/src/test/resources/integration-test/pom.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4x-code-generation</artifactId>
+    <version>@project.version@</version>
+    <relativePath>../../../..</relativePath>
+  </parent>
+
+  <artifactId>plc4j-java-mspec-test</artifactId>
+
+  <name>PLC4J: Driver: Java Mspec Test</name>
+  <description></description>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.plc4x.plugins</groupId>
+        <artifactId>plc4x-maven-plugin</artifactId>
+        <version>${plc4x-code-generation.version}</version>
+        <executions>
+          <execution>
+            <id>generate-driver</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>generate-driver</goal>
+            </goals>
+            <configuration>
+              <protocolName>test</protocolName>
+              <languageName>java</languageName>
+              <outputFlavor>read-write</outputFlavor>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.karaf.tooling</groupId>
+        <artifactId>karaf-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>generate-feature-xml</id>
+            <phase>compile</phase>
+            <goals>
+              <!-- Generate the feature.xml -->
+              <goal>features-generate-descriptor</goal>
+              <!-- Check the feature.xml -->
+              <goal>verify</goal>
+            </goals>
+            <configuration>
+              <enableGeneration>true</enableGeneration>
+              <aggregateFeatures>true</aggregateFeatures>
+            </configuration>
+          </execution>
+          <execution>
+            <id>build-kar</id>
+            <phase>package</phase>
+            <goals>
+              <!--
+                Build a kar archive (Jar containing the feature.xml
+                as well as the module content and it's dependencies.
+              -->
+              <goal>kar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+            <Bundle-Activator>org.apache.plc4x.java.osgi.DriverActivator</Bundle-Activator>
+            <Export-Service>org.apache.plc4x.java.api.PlcDriver,org.apache.plc4x.protocol.test
+            </Export-Service>
+            <Import-Package>
+              com.fasterxml.jackson.annotation;resolution:=optional,
+              *
+            </Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <configuration>
+          <usedDependencies combine.children="append">
+            <usedDependency>org.apache.plc4x:plc4x-code-generation-language-java</usedDependency>
+            <usedDependency>org.apache.plc4x:plc4x-code-generation-protocol-test</usedDependency>
+          </usedDependencies>
+        </configuration>
+      </plugin>
+
+      <!--
+          Make the failsafe execute all integration-tests
+        -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <!--
+                Notice the @ instead of the $ as prefix? That's late evaluation.
+          -->
+          <!--argLine>@{failsafeArgLine}</argLine-->
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+<dependencies>
+  <dependency>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4j-api</artifactId>
+    <version>@project.version@</version>
+  </dependency>
+
+  <dependency>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4j-spi</artifactId>
+    <version>@project.version@</version>
+  </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-transport-tcp</artifactId>
+      <version>@project.version@</version>
+    </dependency>
+
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-buffer</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-annotations</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-utils-test-utils</artifactId>
+      <version>@project.version@</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-language-java</artifactId>
+      <version>@project.version@</version>
+      <!-- Scope is 'provided' as this way it's not shipped with the driver -->
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-test</artifactId>
+      <version>@project.version@</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-test</artifactId>
+      <version>@project.version@</version>
+      <classifier>tests</classifier>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+
+  </dependencies>
+
+</project>
+
diff --git a/code-generation/language-python/src/test/resources/integration-test/src/main/java/org/apache/plc4x/java/test/readwrite/utils/StaticHelper.java b/code-generation/language-python/src/test/resources/integration-test/src/main/java/org/apache/plc4x/java/test/readwrite/utils/StaticHelper.java
new file mode 100644
index 0000000000..cc1f621980
--- /dev/null
+++ b/code-generation/language-python/src/test/resources/integration-test/src/main/java/org/apache/plc4x/java/test/readwrite/utils/StaticHelper.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.plc4x.java.test.readwrite.utils;
+
+import org.apache.plc4x.java.spi.generation.*;
+
+public class StaticHelper {
+
+    public static boolean parseBit(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeBit(WriteBuffer io, boolean data) {
+    }
+
+    public static byte parseByte(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeByte(WriteBuffer io, byte data) {
+    }
+
+    public static byte parseInt8(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeInt8(WriteBuffer io, byte data) {
+    }
+
+    public static short parseUint8(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeUint8(WriteBuffer io, short data) {
+    }
+
+    public static float parseFloat(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeFloat(WriteBuffer io, float data) {
+    }
+
+    public static double parseDouble(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeDouble(WriteBuffer io, double data) {
+    }
+
+    public static String parseString(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeString(WriteBuffer io, String data) {
+    }
+
+    public static short readManualField(ReadBuffer io, short simpleField) {
+        return 0;
+    }
+
+    public static void writeManualField(WriteBuffer io, short simpleFlied) {
+
+    }
+
+    public static short crcInt8(int num) {
+        return (byte) num;
+    }
+
+    public static short crcUint8(int num) {
+        return (short) num;
+    }
+
+}
diff --git a/code-generation/language-python/src/test/resources/settings.xml b/code-generation/language-python/src/test/resources/settings.xml
new file mode 100644
index 0000000000..7e9bc3609a
--- /dev/null
+++ b/code-generation/language-python/src/test/resources/settings.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  -->
+<settings>
+  <profiles>
+    <profile>
+      <id>it-repo</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <repositories>
+        <repository>
+          <id>local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+      <pluginRepositories>
+        <pluginRepository>
+          <id>local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </pluginRepository>
+      </pluginRepositories>
+    </profile>
+  </profiles>
+</settings>
\ No newline at end of file
diff --git a/code-generation/pom.xml b/code-generation/pom.xml
index 2f15b8283f..2eee34a58f 100644
--- a/code-generation/pom.xml
+++ b/code-generation/pom.xml
@@ -37,7 +37,6 @@
     <module>language-base-freemarker</module>
     <module>protocol-base-mspec</module>
     <module>protocol-test</module>
-
     <module>language-java</module>
   </modules>
 
@@ -62,6 +61,13 @@
         <module>language-go</module>
       </modules>
     </profile>
+
+    <profile>
+      <id>with-python</id>
+      <modules>
+        <module>language-python</module>
+      </modules>
+    </profile>
   </profiles>
 
   <dependencies>
diff --git a/sandbox/plc4py/pom.xml b/sandbox/plc4py/pom.xml
index a88d5e46f4..db0587c3e8 100644
--- a/sandbox/plc4py/pom.xml
+++ b/sandbox/plc4py/pom.xml
@@ -65,6 +65,25 @@
         </executions>
       </plugin>
 
+      <plugin>
+        <groupId>org.apache.plc4x.plugins</groupId>
+        <artifactId>plc4x-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>generate-modbus-driver</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>generate-driver</goal>
+            </goals>
+            <configuration>
+              <protocolName>modbus</protocolName>
+              <languageName>python</languageName>
+              <outputFlavor>read-write</outputFlavor>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-remote-resources-plugin</artifactId>
@@ -192,6 +211,15 @@
   </build>
 
   <dependencies>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-language-python</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+      <!-- Scope is 'provided' as this way it's not shipped with the driver -->
+      <scope>provided</scope>
+    </dependency>
+
     <dependency>
       <groupId>org.apache.plc4x</groupId>
       <artifactId>plc4x-protocols-modbus</artifactId>