| // |
| // 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. |
| // |
| |
| using System.Collections.Generic; |
| using System.Linq; |
| using org.apache.plc4net.tools.codegen.model.terms; |
| |
| namespace org.apache.plc4net.tools.codegen.model |
| { |
| /// <summary>A top-level named type in an mspec file.</summary> |
| public abstract class TypeDefinition |
| { |
| public required string Name { get; set; } |
| |
| /// <summary>Attributes on the type header, e.g. |
| /// <c>byteOrder='"BIG_ENDIAN"'</c>.</summary> |
| public Dictionary<string, Term> Attributes { get; } = new Dictionary<string, Term>(); |
| |
| public Term? GetAttribute(string name) => |
| Attributes.TryGetValue(name, out var v) ? v : null; |
| } |
| |
| /// <summary>A parser argument in a type header: <c>(bit response)</c>.</summary> |
| public sealed class Argument |
| { |
| public required string Name { get; set; } |
| public required TypeReference Type { get; set; } |
| } |
| |
| /// <summary> |
| /// A structured type - a <c>[type]</c> or a <c>[discriminatedType]</c>. |
| /// A discriminated parent is the one whose fields include a |
| /// <see cref="fields.TypeSwitchField"/>; its children are also |
| /// <see cref="ComplexTypeDefinition"/> instances, linked by |
| /// <see cref="ParentName"/> / <see cref="DiscriminatorValues"/>. |
| /// </summary> |
| public sealed class ComplexTypeDefinition : TypeDefinition |
| { |
| public List<Argument> Arguments { get; } = new List<Argument>(); |
| |
| public List<fields.Field> Fields { get; } = new List<fields.Field>(); |
| |
| // ── discriminated-type wiring ──────────────────────────── |
| |
| /// <summary>Set on a child type: the parent it extends.</summary> |
| public string? ParentName { get; set; } |
| |
| /// <summary>Set on a child type: the case values, positionally matched |
| /// against the parent's <see cref="fields.TypeSwitchField.Discriminators"/>. |
| /// A null entry is a wildcard (the case listed fewer values).</summary> |
| public IReadOnlyList<Term?>? DiscriminatorValues { get; set; } |
| |
| public bool IsDiscriminatedParent => |
| Fields.Any(f => f is fields.TypeSwitchField); |
| |
| public bool IsDiscriminatedChild => ParentName != null; |
| |
| public fields.TypeSwitchField? TypeSwitch => |
| Fields.OfType<fields.TypeSwitchField>().FirstOrDefault(); |
| |
| /// <summary>The fields that hold a value and therefore get a property |
| /// and a constructor parameter (everything except const / implicit / |
| /// reserved / virtual / typeSwitch and the abstract markers).</summary> |
| public IEnumerable<fields.Field> PropertyFields => |
| Fields.Where(f => f.IsProperty); |
| |
| /// <summary> |
| /// On a discriminated parent: the property fields declared before the |
| /// <c>typeSwitch</c>. A child inherits these - the parent reads and |
| /// writes them, and passes them to the child's <c>StaticParse</c> and |
| /// on to <c>base(...)</c>. |
| /// </summary> |
| public IEnumerable<fields.Field> PrefixPropertyFields => |
| Fields.TakeWhile(f => f is not fields.TypeSwitchField).Where(f => f.IsProperty); |
| |
| /// <summary> |
| /// On a discriminated parent: the property fields declared after the |
| /// <c>typeSwitch</c> - a shared trailer (S7's COTPPacket parameters / |
| /// payload). The child reads and writes these, after its own body. |
| /// </summary> |
| public IEnumerable<fields.Field> SuffixPropertyFields => |
| Fields.SkipWhile(f => f is not fields.TypeSwitchField).Skip(1).Where(f => f.IsProperty); |
| |
| /// <summary> |
| /// On a discriminated parent: the pre-<c>typeSwitch</c> fields that |
| /// carry a value a child might reference in an expression - property |
| /// fields plus discriminators, implicit and const fields. The parent |
| /// reads them and passes them to the child's <c>StaticParse</c>. |
| /// </summary> |
| public IEnumerable<fields.Field> PrefixContextFields => |
| Fields.TakeWhile(f => f is not fields.TypeSwitchField) |
| .Where(f => f.Name != null |
| && f is not fields.ReservedField); |
| |
| /// <summary>Pre-<c>typeSwitch</c> implicit fields - a child recomputes |
| /// these at the top of its serialize / length.</summary> |
| public IEnumerable<fields.ImplicitField> PrefixImplicitFields => |
| Fields.TakeWhile(f => f is not fields.TypeSwitchField).OfType<fields.ImplicitField>(); |
| |
| /// <summary>All fields up to and including the <c>typeSwitch</c> - what |
| /// a discriminated parent's own <c>StaticParse</c> / <c>Serialize</c> |
| /// covers.</summary> |
| public IEnumerable<fields.Field> FieldsThroughSwitch |
| { |
| get |
| { |
| foreach (var f in Fields) |
| { |
| yield return f; |
| if (f is fields.TypeSwitchField) |
| { |
| yield break; |
| } |
| } |
| } |
| } |
| |
| /// <summary>The fields after the <c>typeSwitch</c> in declaration |
| /// order (a superset of <see cref="SuffixPropertyFields"/> - includes |
| /// reserved / const trailers too).</summary> |
| public IEnumerable<fields.Field> FieldsAfterSwitch => |
| Fields.SkipWhile(f => f is not fields.TypeSwitchField).Skip(1); |
| |
| public IEnumerable<fields.ConstField> ConstFields => Fields.OfType<fields.ConstField>(); |
| } |
| |
| /// <summary>An <c>[enum]</c> type.</summary> |
| public sealed class EnumTypeDefinition : TypeDefinition |
| { |
| /// <summary>The wire type: <c>uint 8</c> for |
| /// <c>[enum uint 8 ModbusErrorCode ...]</c>. Null for a bare |
| /// <c>[enum Name ...]</c> (rare; treated as an int).</summary> |
| public SimpleTypeReference? BaseType { get; set; } |
| |
| /// <summary>Per-constant parameters: <c>(uint 8 dataTypeSize)</c>.</summary> |
| public List<Argument> Arguments { get; } = new List<Argument>(); |
| |
| public List<EnumValue> Values { get; } = new List<EnumValue>(); |
| |
| /// <summary><c>[enum PlcValueType external='true']</c>: the type lives |
| /// outside the mspec (hand-written in the SPI). The generator emits no |
| /// file for it and skips any accessor that would return it.</summary> |
| public bool IsExternal => |
| GetAttribute("external") is terms.BooleanLiteral { Value: true } |
| || GetAttribute("external") is terms.StringLiteral { Value: "true" }; |
| } |
| |
| /// <summary>One constant in an enum.</summary> |
| public sealed class EnumValue |
| { |
| public required string Name { get; set; } |
| |
| /// <summary>The wire value, e.g. <c>0x02</c>. Null when the mspec |
| /// left it out (the constant then just names a slot).</summary> |
| public Term? Value { get; set; } |
| |
| /// <summary>Values for the enum's <see cref="EnumTypeDefinition.Arguments"/>, |
| /// positionally: <c>['1' BOOL ['2']]</c> gives <c>dataTypeSize = 2</c>.</summary> |
| public IReadOnlyList<Term> ConstantValues { get; set; } = System.Array.Empty<Term>(); |
| } |
| |
| /// <summary> |
| /// A <c>[dataIo]</c> type - parses to / serializes from an |
| /// <c>IPlcValue</c> rather than a generated class. The <c>typeSwitch</c> |
| /// picks the wire layout from the parser arguments (S7's <c>DataItem</c> |
| /// keys on <c>dataProtocolId</c>); each case yields one <c>PlcValue</c>. |
| /// </summary> |
| public sealed class DataIoTypeDefinition : TypeDefinition |
| { |
| public List<Argument> Arguments { get; } = new List<Argument>(); |
| |
| /// <summary>Set only when the dataIo actually has a <c>typeSwitch</c> |
| /// (always true in practice, but the builder sets this after |
| /// construction, so it is not <c>required</c>).</summary> |
| public fields.TypeSwitchField? TypeSwitch { get; set; } |
| |
| /// <summary>The <c>typeSwitch</c> cases, in declaration order. Unlike a |
| /// discriminated type's children these are not lifted to top-level |
| /// types - the case names repeat (<c>CHAR</c>, <c>STRING</c>, <c>TIME</c> |
| /// each appear twice) and a case is a value shape, not a class.</summary> |
| public List<ComplexTypeDefinition> Cases { get; } = new List<ComplexTypeDefinition>(); |
| } |
| } |