/// <summary> /// Parses the given text to ether an expression or an string /// </summary> /// <param name="expression"></param> /// <param name="context"></param> /// <returns></returns> public static IMorestachioExpression ParseExpressionOrString(string expression, TokenzierContext context) { if (expression.Length == 0) { context.Errors.Add(new MorestachioSyntaxError( context.CurrentLocation.AddWindow(new CharacterSnippedLocation(0, 0, expression)), "", "", "", "expected ether an path expression or an string value")); return(null); } if (Tokenizer.IsStringDelimiter(expression[0])) { //its a string constant if (!Tokenizer.IsStringDelimiter(expression[expression.Length - 1])) { context.Errors.Add(new MorestachioSyntaxError( context.CurrentLocation.AddWindow(new CharacterSnippedLocation(0, expression.Length, expression)), "", "", "" + expression[0], "expected " + expression[0])); return(null); } return(MorestachioExpressionString.ParseFrom(expression, 0, context, out _)); } else { return(MorestachioExpression.ParseFrom(expression, context, out _)); } }
/// <summary> /// Parses a text into an Expression string. Must start with ether " or ' /// </summary> /// <param name="text"></param> /// <param name="offset"></param> /// <param name="context"></param> /// <param name="index"></param> /// <returns></returns> public static MorestachioExpressionString ParseFrom(string text, int offset, TokenzierContext context, out int index) { var result = new MorestachioExpressionString(context.CurrentLocation, text[offset]); var isEscapeChar = false; var sb = new StringBuilder(); //skip the string delimiter for (index = offset + 1; index < text.Length; index++) { var c = text[index]; if (isEscapeChar) { sb.Append(c); if (c == result.Delimiter) { isEscapeChar = false; } } else { if (c == '\\') { isEscapeChar = true; } else if (c == result.Delimiter) { if (offset == 0 && index + 1 != text.Length) { context.Errors.Add(new MorestachioSyntaxError( context .CurrentLocation .Offset(index) .AddWindow(new CharacterSnippedLocation(0, index, text)), "", c.ToString(), "did not expect " + result.Delimiter)); break; } break; } else { sb.Append(c); } } } var currentPart = new ExpressionStringConstPart(sb.ToString(), context.CurrentLocation); result.StringParts.Add(currentPart); context.AdvanceLocation(text.Length); return(result); }
internal static IMorestachioExpression ParseExpressionFromKind(this XmlReader reader) { IMorestachioExpression exp = null; switch (reader.GetAttribute(ExpressionKindNodeName)) { case "Expression": exp = new MorestachioExpression(); break; case "ExpressionList": exp = new MorestachioExpressionList(); break; case "ExpressionString": exp = new MorestachioExpressionString(); break; } exp.ReadXml(reader); return(exp); }
/// <inheritdoc /> protected bool Equals(MorestachioExpressionString other) { if (Delimiter != other.Delimiter || !Location.Equals(other.Location)) { return(false); } if (StringParts.Count != other.StringParts.Count) { return(false); } for (var index = 0; index < StringParts.Count; index++) { var leftStrPart = StringParts[index]; var rightStrPart = other.StringParts[index]; if (!leftStrPart.Equals(rightStrPart)) { return(false); } } return(true); }
internal static IMorestachioExpression ParseExpressionFromKind(this XmlReader reader) { IMorestachioExpression exp = null; switch (reader.Name) { case "Expression": exp = new MorestachioExpression(); break; case "ExpressionMultiPart": exp = new MorestachioMultiPartExpressionList(); break; case "ExpressionArgList": exp = new MorestachioArgumentExpressionList(); break; case "ExpressionString": exp = new MorestachioExpressionString(); break; case "ExpressionNumber": exp = new MorestachioExpressionNumber(); break; case "ExpressionOperator": exp = new MorestachioOperatorExpression(); break; default: throw new ArgumentOutOfRangeException(nameof(ExpressionKindNodeName)); } exp.ReadXml(reader); return(exp); }
/// <summary> /// Parses the text into one or more expressions /// </summary> /// <param name="text">the path to parse excluding {{ and }}</param> /// <param name="context">The context used to tokenize the text</param> /// <param name="indexVar">the index of where the parsing stoped</param> /// <returns></returns> public static IMorestachioExpression ParseFrom(string text, TokenzierContext context, out int indexVar) { var index = 0; text = text.Trim(); if (!text.Contains("(")) { var expression = new MorestachioExpression(context.CurrentLocation); for (; index < text.Length; index++) { var c = text[index]; if (!expression.PathTokenizer.Add(c, context, index)) { indexVar = 0; return(null); } } expression.CompilePath(context, 0); indexVar = text.Length; context.AdvanceLocation(text.Length); return(expression); } IMorestachioExpression morestachioExpressions = null; HeaderTokenMatch currentScope = null; //this COULD be made with regexes, i have made it and rejected it as it was no longer readable in any way. var tokenScopes = new Stack <HeaderTokenMatch>(); tokenScopes.Push(new HeaderTokenMatch { State = TokenState.DecideArgumentType, TokenLocation = context.CurrentLocation }); //var currentPathPart = new StringBuilder(); char formatChar; int SkipWhitespaces() { if (Tokenizer.IsWhiteSpaceDelimiter(formatChar)) { return(Seek(f => !Tokenizer.IsWhiteSpaceDelimiter(f), true)); } return(index); } int Seek(Func <char, bool> condition, bool includeCurrent) { var idx = index; if (!includeCurrent) { if (idx + 1 >= text.Length) { return(idx); } idx++; } for (; idx < text.Length; idx++) { formatChar = text[idx]; if (condition(formatChar)) { return(idx); } } return(idx); } bool Eoex() { index = SkipWhitespaces(); return(index + 1 == text.Length); } char?SeekNext(out int nIndex) { nIndex = Seek(f => Tokenizer.IsExpressionChar(f) || Tokenizer.IsPathDelimiterChar(f), false); if (nIndex != -1) { return(text[nIndex]); } return(null); } char[] Take(Func <char, bool> condition, out int idx) { idx = index; var chrs = new List <char>(); for (int i = idx; i < text.Length; i++) { var c = text[i]; idx = i; if (!condition(c)) { break; } chrs.Add(c); } return(chrs.ToArray()); } void TerminateCurrentScope(bool tryTerminate = false) { if ((tryTerminate && tokenScopes.Any()) || !tryTerminate) { tokenScopes.Pop(); } } int EndParameterBracket() { var parent = currentScope.Parent?.Parent; char?seekNext; while (_closingChars.Contains(seekNext = SeekNext(out var seekIndex))) { index = seekIndex; if (seekNext == ')') //the next char is also an closing bracket so there is no next parameter nor an followup expression { //there is nothing after this expression so close it TerminateCurrentScope(); HeaderTokenMatch scope = null; if (tokenScopes.Any()) { scope = tokenScopes.Peek(); } if (scope?.Value is MorestachioExpressionList) //if the parent expression is a list close that list too { TerminateCurrentScope(); parent = parent?.Parent; } parent = parent?.Parent; //set the new parent for followup expression } else { //there is something after this expression if (seekNext == '.') //the next char indicates a followup expression { HeaderTokenMatch scope = null; if (tokenScopes.Any()) { scope = tokenScopes.Peek(); } if (scope != null && scope.Parent != null) //this is a nested expression { if ((scope.Parent?.Value is MorestachioExpressionList)) //the parents parent expression is already a list so close the parent { scope = scope.Parent; TerminateCurrentScope(); } else if (!(scope.Value is MorestachioExpressionList)) //the parent is not an list expression so replace the parent with a list expression { var oldValue = scope.Value as MorestachioExpression; scope.Value = new MorestachioExpressionList(new List <IMorestachioExpression> { oldValue }); var parValue = (scope.Parent.Value as MorestachioExpression); var hasFormat = parValue.Formats.FirstOrDefault(f => f.MorestachioExpression == oldValue); if (hasFormat != null) { hasFormat.MorestachioExpression = scope.Value; } } parent = scope; } else { //we are at root level no need to do anything as the root is already a list expression TerminateCurrentScope(true); } } else { //the next char indicates a new parameter so close this expression and allow next TerminateCurrentScope(true); } if (!Eoex()) { HeaderTokenMatch item; if (parent != null) { //if there is a parent set then this indicates a new argument item = new HeaderTokenMatch { State = TokenState.ArgumentStart, Parent = parent, TokenLocation = context.CurrentLocation.Offset(index + 1) }; } else { index++; index = SkipWhitespaces(); if (!Tokenizer.IsStartOfExpressionPathChar(formatChar) && formatChar != ')') { context.Errors.Add(new InvalidPathSyntaxError( context.CurrentLocation.Offset(index) .AddWindow(new CharacterSnippedLocation(1, index, text)), currentScope.Value.ToString())); return(text.Length); } if (morestachioExpressions != null) { if (formatChar != '.') { context.Errors.Add(new InvalidPathSyntaxError( context.CurrentLocation.Offset(index) .AddWindow(new CharacterSnippedLocation(1, index, text)), currentScope.Value.ToString())); return(text.Length); } } index--; //currentScope.State = TokenState.DecideArgumentType; //if there is no parent set this indicates a followup expression item = new HeaderTokenMatch { State = TokenState.DecideArgumentType, Parent = parent, Value = new MorestachioExpression(context.CurrentLocation.Offset(index)), TokenLocation = context.CurrentLocation.Offset(index + 1) }; } tokenScopes.Push(item); } if (seekNext == '.') { index--; } break; } if (Eoex()) { TerminateCurrentScope(true); break; } } return(index); } for (index = 0; index < text.Length; index++) { currentScope = tokenScopes.Peek(); formatChar = text[index]; switch (currentScope.State) { case TokenState.ArgumentStart: { //we are at the start of an argument index = SkipWhitespaces(); if (formatChar == '[') { index++; currentScope.ArgumentName = new string(Take(f => f != ']', out var idxa)); index = idxa + 1; } index--; //reprocess the char currentScope.State = TokenState.DecideArgumentType; } break; case TokenState.DecideArgumentType: { //we are at the start of an argument index = SkipWhitespaces(); var idx = index; if (Tokenizer.IsStringDelimiter(formatChar)) { //this is an string var cidx = context.Character; currentScope.Value = MorestachioExpressionString.ParseFrom(text, index, context, out index); context.SetLocation(cidx); currentScope.State = TokenState.Expression; } else if (Tokenizer.IsExpressionChar(formatChar)) { currentScope.State = TokenState.Expression; //this is the first char of an expression. index--; currentScope.Value = new MorestachioExpression(context.CurrentLocation.Offset(index)); } else { //this is not the start of an expression and not a string context.Errors.Add(new InvalidPathSyntaxError( context.CurrentLocation.Offset(index) .AddWindow(new CharacterSnippedLocation(1, index, text)), currentScope.Value.ToString())); indexVar = 0; return(null); } if (currentScope.Parent == null) { if (morestachioExpressions == null) { morestachioExpressions = currentScope.Value; } else { if (morestachioExpressions is MorestachioExpressionList expList) { expList.Add(currentScope.Value); } else { var oldExp = morestachioExpressions; morestachioExpressions = new MorestachioExpressionList(new List <IMorestachioExpression>() { oldExp, currentScope.Value }) { Location = oldExp.Location }; } } } else { if (currentScope.Parent?.Value is MorestachioExpression exp) { exp.Formats.Add( new ExpressionArgument(context.CurrentLocation.Offset(idx)) { MorestachioExpression = currentScope.Value, Name = currentScope.ArgumentName }); } if (currentScope.Parent?.Value is MorestachioExpressionList expList) { expList.Add(currentScope.Value); } } } break; case TokenState.Expression: { index = SkipWhitespaces(); if (formatChar == '(') { //in this case the current path has ended and we must prepare for arguments //if this scope was opened multible times, set an error if (currentScope.BracketsCounter > 1) { context.Errors.Add(new MorestachioSyntaxError(context.CurrentLocation.Offset(index) .AddWindow(new CharacterSnippedLocation(1, index, text)), "Format", "(", "Name of Formatter", "Did expect to find the name of a formatter but found single path. Did you forgot to put an . before the 2nd formatter?")); indexVar = 0; return(null); } var currentExpression = currentScope.Value as MorestachioExpression; currentExpression.CompilePath(context, index); if (currentExpression.PathParts.Count == 0) { //in this case there are no parts in the path that indicates ether {{(}} or {{data.(())}} context.Errors.Add(new MorestachioSyntaxError(context.CurrentLocation.Offset(index) .AddWindow(new CharacterSnippedLocation(1, index, text)), "Format", "(", "Name of Formatter", "Did expect to find the name of a formatter but found single path. Did you forgot to put an . before the 2nd formatter?")); indexVar = 0; return(null); } //get the last part of the path as the name of the formatter currentExpression.FormatterName = currentExpression.PathTokenizer.GetFormatterName(); currentScope.BracketsCounter++; //seek the next non whitespace char. That should be ether " or an expression char index = Seek(f => !Tokenizer.IsWhiteSpaceDelimiter(f), false); if (formatChar == ')') { //the only next char is the closing bracket so no arguments currentScope.BracketsCounter--; index = EndParameterBracket(); } else { //indicates the start of an argument index--; tokenScopes.Push(new HeaderTokenMatch { State = TokenState.ArgumentStart, Parent = currentScope }); } } else if (formatChar == ')') { ////close the current scope. This scope is an parameter expression //TerminateCurrentScope(); var parentExpression = currentScope.Parent?.Value as MorestachioExpression; currentScope.Parent.BracketsCounter--; if (currentScope.Value is MorestachioExpression currentScopeValue) { currentScopeValue.CompilePath(context, index); if (currentScopeValue != null && !currentScopeValue.PathParts.Any() && parentExpression?.Formats.Any() == true) { context.Errors.Add(new InvalidPathSyntaxError( context.CurrentLocation.Offset(index) .AddWindow(new CharacterSnippedLocation(1, index, text)), currentScope.Value.ToString())); } } TerminateCurrentScope(); index = EndParameterBracket(); } else if (formatChar == ',') { if (currentScope.Value is MorestachioExpression currentScopeValue) { currentScopeValue.CompilePath(context, index); if (currentScopeValue != null && !currentScopeValue.PathParts.Any()) { context.Errors.Add( new InvalidPathSyntaxError(currentScopeValue.Location .AddWindow(new CharacterSnippedLocation(1, index, text)), ",")); } } TerminateCurrentScope(); //add a new one into the stack as , indicates a new argument tokenScopes.Push(new HeaderTokenMatch { State = TokenState.ArgumentStart, Parent = currentScope.Parent }); } else if (currentScope.BracketsCounter == 0) { //we are in an path expression //like data.data.data.data if ((currentScope.Value as MorestachioExpression)?.PathTokenizer.Add(formatChar, context, index) == false) { indexVar = 0; return(null); } if (Eoex()) { //an expression can be ended just at any time //it just should not end with an . if (formatChar == '.') { context.Errors.Add(new MorestachioSyntaxError(context.CurrentLocation.Offset(index) .AddWindow(new CharacterSnippedLocation(1, index, text)), "Format", "(", "Name of Formatter", "Did not expect a . at the end of an expression without an formatter")); } (currentScope.Value as MorestachioExpression).CompilePath(context, index); TerminateCurrentScope(); } } } break; } } if (tokenScopes.Any()) { context.Errors.Add(new InvalidPathSyntaxError(context.CurrentLocation.Offset(index) .AddWindow(new CharacterSnippedLocation(1, index, text)), text)); } context.AdvanceLocation(index); indexVar = index; return(morestachioExpressions); }
public ExpressionDebuggerDisplay(MorestachioExpressionString exp) { _exp = exp; }
/// <summary> /// Parses a text into an Expression string. Must start with ether " or ' /// </summary> /// <param name="text"></param> /// <param name="offset"></param> /// <param name="context"></param> /// <param name="index"></param> /// <returns></returns> public static MorestachioExpressionString ParseFrom(string text, int offset, TokenzierContext context, out int index) { var result = new MorestachioExpressionString() { Location = context.CurrentLocation }; var isEscapeChar = false; var currentPart = new ExpressionStringConstPart() { Location = context.CurrentLocation, PartText = string.Empty }; //get the string delimiter thats ether " or ' result.Delimiter = text[offset]; result.StringParts.Add(currentPart); //skip the string delimiter for (index = offset + 1; index < text.Length; index++) { var c = text[index]; if (isEscapeChar) { currentPart.PartText += c; if (c == result.Delimiter) { isEscapeChar = false; } } else { if (c == '\\') { isEscapeChar = true; } else if (c == result.Delimiter) { if (offset == 0 && index + 1 != text.Length) { context.Errors.Add(new MorestachioSyntaxError( context .CurrentLocation .Offset(index) .AddWindow(new CharacterSnippedLocation(0, index, text)), "", c.ToString(), "did not expect " + result.Delimiter)); break; } break; } else { currentPart.PartText += c; } } } context.AdvanceLocation(text.Length); return(result); }