using System; using System.Collections.Generic; using System.Diagnostics.SymbolStore; using System.Globalization; using System.IO; using System.Linq; using System.Net.Http.Headers; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using static SignalDescriptionLanguage.SDLParser; namespace SignalDescriptionLanguage { public static class SDLParser { const string numExpr = "(0x)?[0-9a-fA-F]+"; const string valExpr = $"([hlHL!=]{numExpr})"; const string byteExpr = $"([bB]{numExpr})"; const string strExpr = "([sS]\"([^\"\\\\]|\\\\.)+\")"; const string nameExpr = "(\\[[0-9a-zA-Z]+\\])"; static Regex initialReg = new Regex("^\\$[01]$"); static Regex valueReg = new Regex($"^{valExpr}$"); static Regex byteReg = new Regex($"^{byteExpr}$"); static Regex strReg = new Regex($"^{strExpr}$"); static Regex nameReg = new Regex($"^{nameExpr}$"); static Regex groupReg = new($"^{{((<[0-9a-zA-Z]+>,)?((\\s*({nameExpr}|{valExpr}|{byteExpr}|{strExpr})\\s*[,}}])+))(?<=}})({numExpr})$"); static Regex commentReg = new Regex("//.*$", RegexOptions.Multiline); static Regex commentBlockReg = new Regex("/\\*.*?\\*/"); public static IToken[] GetTokens(string Input) { string clean = commentReg.Replace(Input, ""); clean = clean.Replace("\r", "").Replace("\n", ""); clean = commentBlockReg.Replace(clean, ""); string semicolonScape = Guid.NewGuid().ToString(); var escaped = clean.Replace("\\;", semicolonScape); string[] sTokens = escaped.Split(';', StringSplitOptions.RemoveEmptyEntries); for (int buc = 0; buc < sTokens.Length; buc++) sTokens[buc] = sTokens[buc].Trim().Replace(semicolonScape, "\\;"); List tokens = new List(); foreach(var token in sTokens) { var type = GetTokenType(token); switch(type) { case TokenType.InitialValue: tokens.Add(new InitialToken(token)); break; case TokenType.Value: tokens.Add(new ValueToken(token)); break; case TokenType.Byte: tokens.Add(new ByteToken(token)); break; case TokenType.String: tokens.Add(new StringToken(token)); break; case TokenType.Group: tokens.Add(new GroupToken(token)); break; case TokenType.GroupName: tokens.Add(new GroupNameToken(token)); break; default: throw new InvalidTokenException($"Invalid token found (\"{token}\")."); } } return tokens.ToArray(); } public static TokenType GetTokenType(string Token) { if(initialReg.IsMatch(Token)) return TokenType.InitialValue; if (valueReg.IsMatch(Token)) return TokenType.Value; if (byteReg.IsMatch(Token)) return TokenType.Byte; if (strReg.IsMatch(Token)) return TokenType.String; if (nameReg.IsMatch(Token)) return TokenType.GroupName; if(groupReg.IsMatch(Token)) return TokenType.Group; return TokenType.None; } public static bool[] GetSamples(ValueToken Token, ref bool SignalState) { bool[] samples = new bool[Token.Value]; switch (Token.ValueType) { case ValueTokenType.High: SignalState = true; break; case ValueTokenType.Low: SignalState = false; break; case ValueTokenType.Equal: break; case ValueTokenType.Invert: SignalState = !SignalState; break; default: throw new InvalidTokenException($"Invalid value token type: {Token.ValueType}"); } Array.Fill(samples, SignalState); return samples; } public static bool[] GetSamples(ByteToken Token, IEnumerable NamedGroups, ref bool SignalState) { return GetByteSamples(Token.Value, Token.ByteType == ByteTokenType.ByteLM, NamedGroups, ref SignalState); } public static bool[] GetSamples(StringToken Token, IEnumerable NamedGroups, ref bool SignalState) { string unEscaped = UnescapeString(Token.Value); byte[] bytes = Encoding.ASCII.GetBytes(unEscaped); List samples = new List(); foreach (byte b in bytes) samples.AddRange(GetByteSamples(b, Token.StringType == StringTokenType.StringLM, NamedGroups, ref SignalState)); return samples.ToArray(); } private static string UnescapeString(string Value) { return Value.Replace("\\a", "\a") .Replace("\\b", "\b") .Replace("\\f", "\f") .Replace("\\n", "\n") .Replace("\\r", "\r") .Replace("\\t", "\t") .Replace("\\v", "\v") .Replace("\\\\", "\\") .Replace("\\\"", "\"") .Replace("\\;", ";") .Replace("\\,", ","); } private static bool[] GetByteSamples(byte Value, bool LSBtoMSB, IEnumerable NamedGroups, ref bool SignalState) { List byteSamples = new List(); var zeroGroup = NamedGroups.FirstOrDefault(g => g.GroupName == "0"); var oneGroup = NamedGroups.FirstOrDefault(g => g.GroupName == "1"); if (zeroGroup == null || oneGroup == null) throw new MissingGroupException("Found byte/string value but groups \"0\"/\"1\" are missing."); for (int buc = 0; buc < 8; buc++) { if ((Value & (LSBtoMSB ? (1 << buc) : (128 >> buc))) == 0) byteSamples.AddRange(GetSamples(zeroGroup, NamedGroups, ref SignalState)); else byteSamples.AddRange(GetSamples(oneGroup, NamedGroups, ref SignalState)); } return byteSamples.ToArray(); } public static bool[] GetSamples(GroupNameToken Token, IEnumerable NamedGroups, ref bool SignalState) { var group = NamedGroups.FirstOrDefault(g => g.GroupName == Token.Name); if (group == null) throw new MissingGroupException($"Cannot find a group named \"{Token.Name}\"."); return GetSamples(group, NamedGroups, ref SignalState); } public static bool[] GetSamples(GroupToken Group, IEnumerable NamedGroups, ref bool SignalState) { List samples = new List(); for (int buc = 0; buc < Group.Repeats; buc++) { foreach (var token in Group.Tokens) { switch (token.TokenType) { case TokenType.Value: samples.AddRange(GetSamples((ValueToken)token, ref SignalState)); break; case TokenType.Byte: samples.AddRange(GetSamples((ByteToken)token, NamedGroups, ref SignalState)); break; case TokenType.String: samples.AddRange(GetSamples((StringToken)token, NamedGroups, ref SignalState)); break; case TokenType.GroupName: samples.AddRange(GetSamples((GroupNameToken)token, NamedGroups, ref SignalState)); break; default: throw new InvalidTokenException($"Found invalid token in group: \"{token.Source}\""); } } } return samples.ToArray(); } private static int NumberParse(string Input) { if (string.IsNullOrWhiteSpace(Input)) throw new InvalidNumberException($"Number has an incorrect value: {Input}"); if (Input.Length > 2 && Input.Substring(0, 2).ToLower() == "0x") { if (Input.Length < 3) throw new InvalidNumberException($"Number has an incorrect value: {Input}"); string hexNum = Input.Substring(2, Input.Length - 2); int res; if (!int.TryParse(hexNum, System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture, out res)) throw new InvalidNumberException($"Cannot parse hex number: {Input}"); return res; } else { int res; if (!int.TryParse(Input, out res)) throw new InvalidNumberException($"Cannot parse decimal number: {Input}"); return res; } } #region Enumerations public enum TokenType { None, InitialValue, Group, GroupName, Value, Byte, String } public enum ValueTokenType { None, High, Low, Invert, Equal } public enum ByteTokenType { ByteLM, ByteML } public enum StringTokenType { StringLM, StringML } #endregion #region Token definitions public interface IToken { public string Source { get; } public TokenType TokenType { get; } } public class InitialToken : IToken { public string Source { get; private set; } public InitialToken(string Input) { if (string.IsNullOrWhiteSpace(Input) || Input.Length < 2 || GetTokenType(Input) != TokenType.InitialValue) throw new InvalidTokenException($"Token is not an initial token (\"{Input}\")."); int val = NumberParse(Input.Substring(1)); if (val < 0 || val > 1) throw new InvalidTokenException("Start condition must be \"0\" or \"1\"."); Value = val == 0 ? false : true; Source = Input; } public TokenType TokenType { get { return TokenType.InitialValue; } } public bool Value { get; set; } } public class ValueToken : IToken { public string Source { get; private set; } public ValueToken(string Input) { if (string.IsNullOrWhiteSpace(Input) || Input.Length < 2 || GetTokenType(Input) != TokenType.Value) throw new InvalidTokenException($"Token is not a value (\"{Input}\")."); string valType = Input.Substring(0, 1); int val = NumberParse(Input.Substring(1)); if (val < 0) throw new InvalidTokenException($"Value out of range (\"{Input}\")"); switch (valType) { case "h": case "H": ValueType = ValueTokenType.High; break; case "l": case "L": ValueType = ValueTokenType.Low; break; case "!": ValueType = ValueTokenType.Invert; break; case "=": ValueType = ValueTokenType.Equal; break; default: throw new InvalidTokenException("Token is not a value."); } Value = val; Source = Input; } public TokenType TokenType { get { return TokenType.Value; } } public ValueTokenType ValueType { get; set; } public int Value { get; set; } } public class ByteToken : IToken { public string Source { get; private set; } public ByteToken(string Input) { if (string.IsNullOrWhiteSpace(Input) || Input.Length < 2 || GetTokenType(Input) != TokenType.Byte) throw new InvalidTokenException($"Token is not a byte (\"{Input}\")."); string valType = Input.Substring(0, 1); int val = NumberParse(Input.Substring(1)); if (val < 0 || val > 255) throw new InvalidTokenException($"Byte value out of range (\"{Input}\")"); if (valType == "b") ByteType = ByteTokenType.ByteLM; else ByteType = ByteTokenType.ByteML; Value = (byte)val; Source = Input; } public TokenType TokenType { get { return TokenType.Byte; } } public ByteTokenType ByteType { get; set; } public byte Value { get; set; } } public class StringToken : IToken { public string Source { get; private set; } public StringToken(string Input) { if (string.IsNullOrWhiteSpace(Input) || Input.Length < 4 || GetTokenType(Input) != TokenType.String) throw new InvalidTokenException($"Token is not a string (\"{Input}\")."); string valType = Input.Substring(0, 1); string val = Input.Substring(2,Input.Length - 3); if (valType == "s") StringType = StringTokenType.StringLM; else StringType = StringTokenType.StringML; Value = val; Source = Input; } public TokenType TokenType { get { return TokenType.String; } } public StringTokenType StringType { get; set; } public string Value { get; set; } } public class GroupNameToken : IToken { public string Source { get; private set; } public GroupNameToken(string Input) { if (string.IsNullOrWhiteSpace(Input) || Input.Length < 3 || GetTokenType(Input) != TokenType.GroupName) throw new InvalidTokenException($"Token is not a group name (\"{Input}\")."); Name = Input.Substring(1, Input.Length - 2); Source = Input; } public TokenType TokenType { get { return TokenType.GroupName; } } public string Name { get; set; } } public class GroupToken : IToken { public string Source { get; private set; } public GroupToken(string Input) { if (string.IsNullOrWhiteSpace(Input) || Input.Length < 3 || GetTokenType(Input) != TokenType.Group) throw new InvalidTokenException($"Token is not a group (\"{Input}\")."); var match = groupReg.Match(Input); string commaScape = Guid.NewGuid().ToString(); string escaped = match.Groups[3].Value.Replace("\\,", commaScape); string[] internalTokens = escaped.Replace("}", "").Split(','); for(int buc = 0; buc < internalTokens.Length;buc++) internalTokens[buc] = internalTokens[buc].Trim().Replace(commaScape, "\\,"); if (!string.IsNullOrWhiteSpace(match.Groups[2].Value)) GroupName = match.Groups[2].Value.Replace("<", "").Replace(">", "").Replace(",", ""); List tokens = new List(); foreach (var token in internalTokens) { switch (GetTokenType(token)) { case TokenType.Value: tokens.Add(new ValueToken(token)); break; case TokenType.Byte: tokens.Add(new ByteToken(token)); break; case TokenType.String: tokens.Add(new StringToken(token)); break; case TokenType.GroupName: tokens.Add(new GroupNameToken(token)); break; default: throw new InvalidTokenException($"Invalid token in group (\"{token}\")."); } } if(tokens.Count == 0) throw new InvalidGroupException($"Group contains no tokens (\"{Input}\")."); Repeats = int.Parse(match.Groups[13].Value); Tokens = tokens.ToArray(); Source = Input; } public TokenType TokenType { get { return TokenType.Group; } } public string? GroupName { get; set; } public IToken[] Tokens { get; set; } public int Repeats { get; set; } } #endregion /// /// Tokenized SDL class, used to parse and convert a text source to tokens and samples /// public class TokenizedSDL { string source; IToken[] tokens; public string Source { get { return source; } } public IToken[] Tokens { get { return tokens; } } public bool? InitialValue { get { return (tokens.FirstOrDefault(t => t.TokenType == TokenType.InitialValue) as InitialToken)?.Value; } } public ValueToken[] Values { get { return tokens.Where(t => t.TokenType == TokenType.Value) .Cast().ToArray(); } } public ByteToken[] Bytes { get { return tokens.Where(t => t.TokenType == TokenType.Byte) .Cast().ToArray(); } } public StringToken[] Strings { get { return tokens.Where(t => t.TokenType == TokenType.String) .Cast().ToArray(); } } public GroupNameToken[] GroupNames { get { return tokens.Where(t => t.TokenType == TokenType.GroupName) .Cast().ToArray(); } } public GroupToken[] Groups { get { return tokens.Where(t => t.TokenType == TokenType.Group) .Cast().ToArray(); } } public GroupToken[] NamedGroups { get { return tokens.Where(t => t.TokenType == TokenType.Group && (!string.IsNullOrWhiteSpace((t as GroupToken).GroupName))) .Cast().ToArray(); } } public TokenizedSDL(string Input) { source = Input; tokens = GetTokens(Input); var values = Values; var names = GroupNames; var groups = Groups; var namedGroups = NamedGroups; var missingNames = names.Where(n => !namedGroups.Any(ng => ng.GroupName == n.Name)).Select(n => n.Name).ToArray(); var missingNamesInGroups = groups.SelectMany(g => g.Tokens .Where(t => t.TokenType == TokenType.GroupName) .Cast() .Where(n => !namedGroups.Any(ng => ng.GroupName == n.Name)) .Select(n => n.Name)) .ToArray(); List missing = new List(); if (missingNames != null) missing.AddRange(missingNames); if (missingNamesInGroups != null) missing.AddRange(missingNamesInGroups); if (missing.Count > 0) { string missingString = string.Join(", ", missing.Distinct()); throw new MissingGroupException($"Missing named groups: {missingString}"); } var needsBitGroups = (Bytes != null && Bytes.Length > 0) || (Strings != null && Strings.Length > 0); if (needsBitGroups) { if (!NamedGroups.Any(g => g.GroupName == "0") || !NamedGroups.Any(g => g.GroupName == "1")) throw new MissingGroupException($"Definition contains bytes/strings but groups \"0\"/\"1\" are not defined."); } var duplicatedNames = namedGroups.GroupBy(g => g.GroupName) .Where(gg => gg.Count() > 1) .Select(gg => gg.Key) .ToArray(); if (duplicatedNames != null && duplicatedNames.Length > 0) throw new DuplicatedGroupException($"Found duplicated named groups: {string.Join(", ", duplicatedNames)}"); } public bool[] ToSamples(bool? InitialValue = null) { bool currentState = InitialValue ?? (this.InitialValue ?? false); var namedGroups = NamedGroups; List samples = new List(); foreach (var token in Tokens) { switch(token.TokenType) { case TokenType.Value: samples.AddRange(GetSamples((ValueToken)token, ref currentState)); break; case TokenType.Byte: samples.AddRange(GetSamples((ByteToken)token, namedGroups, ref currentState)); break; case TokenType.String: samples.AddRange(GetSamples((StringToken)token, namedGroups, ref currentState)); break; case TokenType.GroupName: samples.AddRange(GetSamples((GroupNameToken)token, namedGroups, ref currentState)); break; case TokenType.Group: var group = (GroupToken)token; if (!string.IsNullOrWhiteSpace(group.GroupName)) continue; samples.AddRange(GetSamples(group, namedGroups, ref currentState)); break; } } return samples.ToArray(); } } #region Exceptions public class MissingGroupException : Exception { public MissingGroupException(string message) : base(message) { } } public class DuplicatedGroupException : Exception { public DuplicatedGroupException(string message) : base(message) { } } public class InvalidTokenException : Exception { public InvalidTokenException(string message) : base(message) { } } public class InvalidGroupException : Exception { public InvalidGroupException(string message) : base(message) { } } public class InvalidNumberException : Exception { public InvalidNumberException(string message) : base(message) { } } #endregion } }