blob: 0ec990074fa58f7f32712e33682510a660748f98 [file]
//
// 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>();
}
}