| // | |
| // 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; | |
| using org.apache.plc4net.tools.codegen.model.fields; | |
| namespace org.apache.plc4net.tools.codegen.output | |
| { | |
| /// <summary> | |
| /// Resolves the names in an mspec expression against one complex type's | |
| /// fields and parser arguments. During parse, a field name is a local | |
| /// holding the value just read; during serialize, it is the property. An | |
| /// enum member is <c>Enum.Member</c>; an unknown name is left alone. | |
| /// </summary> | |
| internal sealed class FieldScope : IExpressionScope | |
| { | |
| private readonly Dictionary<string, TypeReference?> _byName = new Dictionary<string, TypeReference?>(); | |
| private readonly HashSet<string> _enumNames; | |
| // constant name -> "EnumName.CONSTANT" (first enum wins on a clash) | |
| private readonly Dictionary<string, string> _enumConstants; | |
| // every enum attribute-accessor name, e.g. `sizeInBits`, `code` - a | |
| // member access on one of these is a Get… extension-method call. | |
| private readonly HashSet<string> _enumAccessors; | |
| private readonly bool _serialize; | |
| public FieldScope(Protocol protocol, ComplexTypeDefinition type, bool serialize = false) | |
| { | |
| _serialize = serialize; | |
| _hoistedLocals = new HashSet<string>(); | |
| _enumNames = new HashSet<string>(protocol.Enums.Select(e => e.Name)); | |
| _enumAccessors = new HashSet<string>( | |
| protocol.Enums.SelectMany(e => e.Arguments.Select(a => a.Name))); | |
| _enumConstants = new Dictionary<string, string>(); | |
| foreach (var e in protocol.Enums) | |
| { | |
| foreach (var v in e.Values) | |
| { | |
| _enumConstants.TryAdd(v.Name, $"{e.Name}.{v.Name}"); | |
| } | |
| } | |
| var owner = type.ParentName != null ? protocol.FindType(type.ParentName) : type; | |
| if (owner != null) | |
| { | |
| foreach (var a in owner.Arguments) | |
| { | |
| _byName[a.Name] = a.Type; | |
| } | |
| foreach (var f in owner.Fields.Where(f => f.Name != null)) | |
| { | |
| _byName[f.Name] = f.Type; | |
| } | |
| } | |
| foreach (var f in type.Fields.Where(f => f.Name != null)) | |
| { | |
| _byName[f.Name] = f.Type; | |
| } | |
| } | |
| private FieldScope(FieldScope src, bool serialize) | |
| { | |
| _serialize = serialize; | |
| _enumNames = src._enumNames; | |
| _enumAccessors = src._enumAccessors; | |
| _enumConstants = src._enumConstants; | |
| _byName = src._byName; | |
| _hoistedLocals = src._hoistedLocals; | |
| } | |
| public FieldScope ForSerialize() => new FieldScope(this, serialize: true); | |
| /// <summary>The C# for <c>_value</c> - the field's own value, set by the | |
| /// generator around a <c>manual</c> field's serialize expression.</summary> | |
| public string? CurrentValue { get; set; } | |
| /// <summary>Names the generator has hoisted as a plain local (a | |
| /// recomputed implicit field in a child's serialize / length) - these | |
| /// resolve to the camelCase local, not a Pascal property. Shared with | |
| /// the serialize view so a mark on either is seen by both.</summary> | |
| private readonly HashSet<string> _hoistedLocals; | |
| public void MarkLocal(string name) => _hoistedLocals.Add(name); | |
| public void ClearLocals() => _hoistedLocals.Clear(); | |
| public string? ResolveReference(string name) | |
| { | |
| switch (name) | |
| { | |
| // mspec context variables available inside manual / expression fields | |
| case "readBuffer": | |
| case "writeBuffer": | |
| return name; | |
| case "_lastItem": | |
| // the generator gives a type that needs it a `_lastItem` | |
| // parameter, threaded from the enclosing array loop. | |
| return "_lastItem"; | |
| case "curPos": | |
| case "_curPos": | |
| // mspec curPos is bytes consumed since this type's parse | |
| // began; the generator captures _startPos (bits) at entry. | |
| return _serialize | |
| ? "((writeBuffer.GetPos() - _startPos) / 8)" | |
| : "((readBuffer.GetPos() - _startPos) / 8)"; | |
| case "_value": | |
| return CurrentValue ?? "_value"; | |
| case "_type": | |
| return "this"; | |
| // the length of the type being defined, used bare inside an | |
| // implicit / virtual field's expression | |
| case "lengthInBytes": | |
| return "GetLengthInBytes()"; | |
| case "lengthInBits": | |
| return "GetLengthInBits()"; | |
| } | |
| if (_enumNames.Contains(name)) | |
| { | |
| return name; | |
| } | |
| if (_hoistedLocals.Contains(name)) | |
| { | |
| return CSharpGenerator.Camel(name); | |
| } | |
| if (_byName.ContainsKey(name)) | |
| { | |
| return _serialize ? CSharpGenerator.Pascal(name) : CSharpGenerator.Camel(name); | |
| } | |
| // A bare enum constant used in a discriminator / expression, e.g. | |
| // `BYTE_WORD_DWORD` for a `DataTransportSize`-typed const field. | |
| if (_enumConstants.TryGetValue(name, out var qualified)) | |
| { | |
| return qualified; | |
| } | |
| return null; | |
| } | |
| /// <summary>Resolves only bare enum-constant names - for rendering an | |
| /// enum's own value expressions and its parameter tables, where there | |
| /// are no fields in scope.</summary> | |
| public static IExpressionScope EnumConstantsOnly(Protocol protocol) | |
| => new EnumScope(protocol); | |
| private sealed class EnumScope : IExpressionScope | |
| { | |
| private readonly Dictionary<string, string> _constants = new Dictionary<string, string>(); | |
| public EnumScope(Protocol protocol) | |
| { | |
| foreach (var e in protocol.Enums) | |
| { | |
| foreach (var v in e.Values) | |
| { | |
| _constants.TryAdd(v.Name, $"{e.Name}.{v.Name}"); | |
| } | |
| } | |
| } | |
| public string? ResolveReference(string name) | |
| => _constants.TryGetValue(name, out var q) ? q : null; | |
| } | |
| public string? ResolveMember(string name) | |
| => _enumAccessors.Contains(name) | |
| ? $"Get{CSharpGenerator.Pascal(name)}()" | |
| : null; | |
| public string ResolveCount(string name) | |
| { | |
| var reference = ResolveReference(name) ?? name; | |
| if (_byName.TryGetValue(name, out var type) | |
| && type is SimpleTypeReference { IsByteBased: true }) | |
| { | |
| return reference + ".Length"; | |
| } | |
| // A byte-based array field is byte[]; anything else is List<T>. | |
| return reference + ".Count"; | |
| } | |
| } | |
| } |