/// <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, out TokenzierContext context) { context = new TokenzierContext(Tokenizer.NewlineFinder.Matches(expression).OfType <Match>().Select(k => k.Index) .ToArray()); context.SetLocation(0); return(ParseExpressionOrString(expression, context)); }
public static TokenzierContext FromText(string expression) { var tokenzierContext = new TokenzierContext( Tokenizer.NewlineFinder.Matches(expression).OfType <Match>().Select(k => k.Index) .ToArray()); tokenzierContext.SetLocation(0); return(tokenzierContext); }
/// <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); }