public static IAtom[] Parse(IEnumerable <LexToken> tokens, PenisConfiguration config) { var atoms = ParseInner(tokens, config); CheckSyntax(atoms); return(atoms.ToArray()); }
/// <summary> /// Deserializes from a PENIS-serialized string into an object of type <paramref name="type"/>. /// </summary> /// <param name="type">The object's type.</param> /// <param name="str">The PENIS-serialized object text.</param> /// <param name="config">The configuration to use.</param> public static object DeserializeObject(Type type, string str, PenisConfiguration config = null) { config = config ?? PenisConfiguration.Default; var lex = Lexer.Lex(str.SplitLines(), config); var parsed = Parser.Parse(lex, config); return(DeserializeObject(parsed, type)); }
public static string Unlex(IEnumerable <LexToken> tokens, PenisConfiguration config) { var str = new StringBuilder(); foreach (var token in tokens) { if (token.FirstInLine) { if (str.Length != 0 || token.Type == LexToken.Types.EmptyLine) { str.AppendLine(); } str.Append(new string(IndentationChar, config.IndentationCount * token.IndentLevel)); } switch (token.Type) { case LexToken.Types.Key: str.Append(token.Value + KeyValueSeparator); break; case LexToken.Types.BeginMultilineString: str.Append(" " + BeginMultilineString); break; case LexToken.Types.EndMultilineString: str.Append(EndMultilineString); break; case LexToken.Types.MultilineText: str.Append(token.Value); break; case LexToken.Types.Value: str.Append(" " + token.Value); break; case LexToken.Types.Comment: if (!token.FirstInLine) { str.Append(" "); } str.Append(CommentBegin + token.Value); break; case LexToken.Types.BeginListItem: str.Append(ListItemBegin); break; } } return(str.ToString()); }
public static IEnumerable <LexToken> Lex(string[] lines, PenisConfiguration config) { int i = 0; var state = new State(); foreach (var line in lines) { foreach (var tk in ParseLine(line, i++, state, config)) { yield return(tk); } } }
internal static object ConvertAtom(IAtom atom, Type targetType, PenisConfiguration config) { if (atom is KeyValueAtom kva) { if (targetType.IsEnum && kva.Value is string str) { return(Enum.Parse(targetType, str, config.IgnoreCase)); } return(kva.Value); } else if (atom is ContainerAtom container) { if (container.IsList) { if (!targetType.IsArray) { throw new ConvertException("Found list in file but target field isn't an array."); } var itemType = targetType.GetElementType(); var items = container.Atoms.Select(o => ConvertAtom(o, itemType, config)); var array = (Array)Activator.CreateInstance(targetType, new object[] { container.Atoms.Count }); Array.Copy(items.ToArray(), array, array.Length); return(array); } else { return(DeserializeObject(container.Atoms, targetType)); } } throw new ParserException("Invalid atom found."); }
/// <summary> /// Deserializes from a PENIS-serialized string into an object of type <typeparamref name="T"/>. /// </summary> /// <typeparam name="T">The object's type.</typeparam> /// <param name="str">The PENIS-serialized object text.</param> /// <param name="config">The configuration to use.</param> public static T DeserializeObject <T>(string str, PenisConfiguration config = null) { return((T)DeserializeObject(typeof(T), str, config)); }
/// <summary> /// Serializes <paramref name="obj"/> into a PENIS file using <paramref name="config"/>. /// </summary> /// <param name="obj">The object to be serialized.</param> /// <param name="config">The configuration to use.</param> public static string SerializeObject(object obj, PenisConfiguration config = null) { return(Lexer.Unlex(Parser.Unparse(GetAtoms(obj).NotNull()), config ?? PenisConfiguration.Default)); }
internal static object DeserializeObject(IEnumerable <IAtom> atoms, Type type, PenisConfiguration config = null) { config = config ?? PenisConfiguration.Default; var instance = Activator.CreateInstance(type); var atomsList = new List <IAtom>(atoms); foreach (var member in GetPropsAndFields(type)) { var atom = atomsList.SingleOrDefault(o => o.Key?.Equals(member.Name, config.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) ?? false); if (atom != null) { atomsList.Remove(atom); member.SetValue(instance, ConvertAtom(atom, member.GetMemberType(), config)); } } return(instance); }
private static IEnumerable <IAtom> ParseInner(IEnumerable <LexToken> tokens, PenisConfiguration config) { var linkedTokens = new LinkedList <LexToken>(tokens); LinkedListNode <LexToken> tokenNode = linkedTokens.First; var containerStack = new Stack <(LexToken Token, ContainerAtom Atom)>(); IAtom lastAtom = null; var pendingDecorations = new List <IDecoration>(); do { if (tokenNode == null) { break; } var token = tokenNode.Value; //Check if we exited any containers while (containerStack.Count > 0 && token.IndentLevel <= containerStack.Peek().Token.IndentLevel) { var atom = containerStack.Pop().Atom; if (containerStack.Count > 0) { containerStack.Peek().Atom.Atoms.Add(atom); } else { yield return(lastAtom = atom); } } if (token.Type == LexToken.Types.Key || token.Type == LexToken.Types.BeginListItem) { var nextType = tokenNode.Next?.Value.Type; //Check for simple "key: value" pair or list item if (nextType == LexToken.Types.Value || nextType == LexToken.Types.BeginMultilineString) { object value; if (nextType == LexToken.Types.BeginMultilineString) { var startingToken = tokenNode; tokenNode = tokenNode.Next; string str = ""; while ((tokenNode = tokenNode.Next).Value.Type != LexToken.Types.EndMultilineString) { str += tokenNode.Value.Value + "\n"; } value = str.TrimEnd('\n'); tokenNode = startingToken; } else { value = ParseValue(tokenNode.Next.Value.Value); } var atom = new KeyValueAtom(token.Value, value); if (containerStack.Count > 0) { containerStack.Peek().Atom.Atoms.Add(lastAtom = atom); } else { yield return(lastAtom = atom); } tokenNode = tokenNode.Next; } else //Item container (i.e. object or list) begins { bool isList = tokenNode != linkedTokens.Last && tokenNode.Next.Value.Type == LexToken.Types.BeginListItem; var atom = new ContainerAtom(token.Value, isList); lastAtom = atom; containerStack.Push((token, atom)); } } else if (token.Type == LexToken.Types.EmptyLine) { if (lastAtom != null) { lastAtom.Decorations.Add(new EmptyLineDecoration(EmptyLineDecoration.Positions.After)); } else { pendingDecorations.Add(new EmptyLineDecoration(EmptyLineDecoration.Positions.Before)); } continue; } else if (token.Type == LexToken.Types.Comment) { if (lastAtom != null) { lastAtom.Decorations.Add(new CommentDecoration(token.Value, token.FirstInLine ? CommentDecoration.Positions.After : CommentDecoration.Positions.Inline)); } else { pendingDecorations.Add(new CommentDecoration(token.Value, CommentDecoration.Positions.Before)); } continue; } foreach (var item in pendingDecorations) { lastAtom.Decorations.Add(item); } pendingDecorations.Clear(); } while ((tokenNode = tokenNode.Next) != null); if (containerStack.Count != 0) { (LexToken Token, IAtom Atom)container = default; do { if (containerStack.Count > 0) { container = containerStack.Pop(); } if (containerStack.Count == 0) { yield return(container.Atom); } else { containerStack.Peek().Atom.Atoms.Add(container.Atom); } } while (containerStack.Count > 0); } }
private static IEnumerable <LexToken> ParseLine(string line, int lineIndex, State state, PenisConfiguration config) { if (string.IsNullOrWhiteSpace(line) && !state.InMultiline) { yield return(new LexToken(LexToken.Types.EmptyLine, null, lineIndex, 0, 0, 0, true)); yield break; } int indentation = line.TakeWhile(o => o == IndentationChar).Count() / config.IndentationCount; //line = line.Substring(indentation * IndentationCount); bool onIndentation = true; bool hasAddedAny = false; if (state.InMultiline) { if (line.Trim() == EndMultilineString) { state.InMultiline = false; yield return(new LexToken(LexToken.Types.EndMultilineString, null, lineIndex, 0, line.Length - 1, 0, IsFirst())); } else { //If line begins and ends with quotes, preserve spaces if (line.Length > 1 && line[0] == OpenString && line[line.Length - 1] == CloseString) { line = line.TrimStart(OpenString).TrimEnd(CloseString); } else { line = line.Trim(); } yield return(new LexToken(LexToken.Types.MultilineText, line, lineIndex, 0, line.Length - 1, 0, IsFirst())); } yield break; } for (int i = 0; i < line.Length; i++) { if (line[i] == CommentBegin && (i == 0 || (i > 0 && line[i - 1] != EscapeCharacter))) { string comment = line.Substring(i + 1); yield return(new LexToken(LexToken.Types.Comment, comment, lineIndex, i, line.Length - 1, indentation, IsFirst())); yield break; } else if (line[i] == KeyValueSeparator) { string value = TakeWhile(line, i + 1, o => o != CommentBegin); if (!string.IsNullOrWhiteSpace(value)) { yield return(ParseValue(value, i, 1)); } i += value.Length; } else if (line[i] == ListItemBegin) { yield return(new LexToken(LexToken.Types.BeginListItem, null, lineIndex, i, i + 1, indentation, IsFirst())); string value = TakeWhile(line, i + 1, o => o != CommentBegin); if (!string.IsNullOrWhiteSpace(value)) { yield return(ParseValue(value, i, 0)); } i++; //Skip list item begin char i += value.Length; if (value.EndsWith(" ")) { i--; } } else if (line[i] != IndentationChar || !onIndentation) { onIndentation = false; string key = TakeWhile(line, i, o => o != CommentBegin && o != KeyValueSeparator); yield return(new LexToken(LexToken.Types.Key, key.Trim(), lineIndex, i, i + key.Length, indentation, IsFirst())); i += key.Length - 1; } } LexToken ParseValue(string value, int i, int startOffset) { if (value.Trim() == BeginMultilineString) { state.InMultiline = true; return(new LexToken(LexToken.Types.BeginMultilineString, null, lineIndex, i + startOffset, i + 1 + value.Length, indentation, IsFirst())); } return(new LexToken(LexToken.Types.Value, value.Trim(), lineIndex, i + startOffset, i + 1 + value.Length, indentation, IsFirst())); } bool IsFirst() => hasAddedAny ? false : hasAddedAny = true; }