public static StringSegment ParseJsCallExpression(this StringSegment literal, out JsCallExpression expression, bool filterExpression = false) { literal = literal.ParseIdentifier(out var token); if (!(token is JsIdentifier identifier)) { throw new SyntaxErrorException($"Expected identifier but instead found '{token}'"); } literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty() || literal.GetChar(0) != '(') { var isWhitespaceSyntax = filterExpression && literal.GetChar(0) == ':'; if (isWhitespaceSyntax) { literal = literal.Advance(1); // replace everything after ':' up till new line and rewrite as single string to method var endStringPos = literal.IndexOf("\n"); var endStatementPos = literal.IndexOf("}}"); if (endStringPos == -1 || (endStatementPos != -1 && endStatementPos < endStringPos)) { endStringPos = endStatementPos; } if (endStringPos == -1) { throw new SyntaxErrorException($"Whitespace sensitive syntax did not find a '\\n' new line to mark the end of the statement, near '{literal.SubstringWithElipsis(0,50)}'"); } var originalArg = literal.Subsegment(0, endStringPos).Trim().ToString(); var rewrittenArgs = originalArg.Replace("{", "{{").Replace("}", "}}"); var strArg = new JsLiteral(rewrittenArgs); expression = new JsCallExpression(identifier, strArg); return(literal.Subsegment(endStringPos)); } expression = new JsCallExpression(identifier); return(literal); } literal.Advance(1); literal = literal.ParseArguments(out var args, termination: ')'); expression = new JsCallExpression(identifier, args.ToArray()); return(literal); }
internal static StringSegment ParseArguments(this StringSegment literal, out List <JsToken> arguments, char termination) { arguments = new List <JsToken>(); literal = literal.Advance(1); while (!literal.IsNullOrEmpty()) { literal = literal.AdvancePastWhitespace(); if (literal.GetChar(0) == termination) { literal = literal.Advance(1); break; } literal = literal.ParseJsExpression(out var listValue); arguments.Add(listValue); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { break; } if (literal.GetChar(0) == termination) { literal = literal.Advance(1); break; } literal = literal.AdvancePastWhitespace(); var c = literal.GetChar(0); if (c == termination) { literal = literal.Advance(1); break; } if (c != ',') { throw new ArgumentException($"Unterminated arguments expression near: {literal.SubstringWithElipsis(0, 50)}"); } literal = literal.Advance(1); literal = literal.AdvancePastWhitespace(); } literal = literal.AdvancePastWhitespace(); return(literal); }
private static StringSegment ParseJsConditionalExpression(this StringSegment literal, JsToken test, out JsConditionalExpression expression) { literal = literal.Advance(1); literal = literal.ParseJsExpression(out var consequent); literal = literal.AdvancePastWhitespace(); if (!literal.FirstCharEquals(':')) { throw new SyntaxErrorException($"Expected Conditional ':' but was {literal.DebugFirstChar()}"); } literal = literal.Advance(1); literal = literal.ParseJsExpression(out var alternate); expression = new JsConditionalExpression(test, consequent, alternate); return(literal); }
public static StringSegment ParseJsCallExpression(this StringSegment literal, out JsCallExpression expression, bool filterExpression = false) { literal = literal.ParseIdentifier(out var token); if (!(token is JsIdentifier identifier)) { throw new SyntaxErrorException($"Expected identifier but instead found {token.DebugToken()}"); } literal = literal.AdvancePastWhitespace(); if (literal.FirstCharEquals(WhitespaceArgument)) { literal = literal.Advance(1); literal = literal.ParseWhitespaceArgument(out var argument); expression = new JsCallExpression(identifier, argument); return(literal); } if (literal.StartsWith("=>")) { literal = literal.ParseArrowExpressionBody(new[] { new JsIdentifier("it") }, out var arrowExpr); expression = new JsCallExpression(identifier, arrowExpr); return(literal); } if (!literal.FirstCharEquals('(')) { expression = new JsCallExpression(identifier); return(literal); } literal = literal.Advance(1); literal = literal.ParseArguments(out var args, termination: ')'); expression = new JsCallExpression(identifier, args.ToArray()); return(literal); }
public static StringSegment ParseNextToken(this StringSegment literal, out object value, out JsBinding binding, bool allowWhitespaceSyntax) { binding = null; value = null; var c = (char)0; if (literal.IsNullOrEmpty()) { return(TypeConstants.EmptyStringSegment); } var i = 0; literal = literal.AdvancePastWhitespace(); var firstChar = literal.GetChar(0); if (firstChar == '\'' || firstChar == '"' || firstChar == '`') { i = 1; var hasEscapeChar = false; while (i < literal.Length && ((c = literal.GetChar(i)) != firstChar || literal.GetChar(i - 1) == '\\')) { i++; if (!hasEscapeChar) { hasEscapeChar = c == '\\'; } } if (i >= literal.Length || literal.GetChar(i) != firstChar) { throw new ArgumentException($"Unterminated string literal: {literal}"); } var str = literal.Substring(1, i - 1); value = str; if (hasEscapeChar) { var sb = StringBuilderCache.Allocate(); for (var j = 0; j < str.Length; j++) { // strip the back-slash used to escape quote char in strings var ch = str[j]; if (ch != '\\' || (j + 1 >= str.Length || str[j + 1] != firstChar)) { sb.Append(ch); } } value = StringBuilderCache.ReturnAndFree(sb); } return(literal.Advance(i + 1)); } if (firstChar >= '0' && firstChar <= '9' || (literal.Length >= 2 && (firstChar == '-' || firstChar == '+') && literal.GetChar(1).IsNumericChar())) { i = 1; var hasExponent = false; var hasDecimal = false; while (i < literal.Length && IsNumericChar(c = literal.GetChar(i)) || (hasExponent = (c == 'e' || c == 'E'))) { if (c == '.') { hasDecimal = true; } i++; if (hasExponent) { i += 2; // [e+1]0 while (i < literal.Length && IsNumericChar(literal.GetChar(i))) { i++; } break; } } var numLiteral = literal.Subsegment(0, i); //don't convert into ternary to avoid Type coercion if (hasDecimal || hasExponent) { value = numLiteral.TryParseDouble(out double d) ? d : default(double); } else { value = numLiteral.ParseSignedInteger(); } return(literal.Advance(i)); } if (firstChar == '{') { var map = new Dictionary <string, object>(); literal = literal.Advance(1); while (!literal.IsNullOrEmpty()) { literal = literal.AdvancePastWhitespace(); if (literal.GetChar(0) == '}') { literal = literal.Advance(1); break; } literal = literal.ParseNextToken(out object mapKeyString, out JsBinding mapKeyVar); if (mapKeyVar is JsExpression) { throw new NotSupportedException($"JsExpression '{mapKeyVar?.Binding}' is not a valid Object key."); } var mapKey = mapKeyVar != null ? mapKeyVar.Binding.Value : (string)mapKeyString; if (mapKey != null) { literal = literal.AdvancePastWhitespace(); if (literal.Length > 0 && literal.GetChar(0) == ':') { literal = literal.Advance(1); literal = literal.ParseNextToken(out object mapValue, out JsBinding mapValueBinding); map[mapKey] = mapValue ?? mapValueBinding; } else //shorthand notation { if (literal.Length == 0 || (c = literal.GetChar(0)) != ',' && c != '}') { throw new ArgumentException($"Unterminated object literal near: {literal.SubstringWithElipsis(0, 50)}"); } map[mapKey] = new JsBinding(mapKey); } } literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { break; } if (literal.GetChar(0) == '}') { literal = literal.Advance(1); break; } literal = literal.AdvancePastChar(','); literal = literal.AdvancePastWhitespace(); } value = map; return(literal); } if (firstChar == '[') { var list = new List <object>(); literal = literal.Advance(1); while (!literal.IsNullOrEmpty()) { literal = literal.AdvancePastWhitespace(); if (literal.GetChar(0) == ']') { literal = literal.Advance(1); break; } literal = literal.ParseNextToken(out object mapValue, out JsBinding mapVarRef); list.Add(mapVarRef ?? mapValue); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { break; } if (literal.GetChar(0) == ']') { literal = literal.Advance(1); break; } literal = literal.AdvancePastWhitespace(); c = literal.GetChar(0); if (c == ']') { literal = literal.Advance(1); break; } if (c != ',') { throw new ArgumentException($"Unterminated array literal near: {literal.SubstringWithElipsis(0, 50)}"); } literal = literal.Advance(1); literal = literal.AdvancePastWhitespace(); } literal = literal.AdvancePastWhitespace(); value = list; return(literal); } if (literal.StartsWith("true") && (literal.Length == 4 || !IsValidVarNameChar(literal.GetChar(4)))) { value = true; return(literal.Advance(4)); } if (literal.StartsWith("false") && (literal.Length == 5 || !IsValidVarNameChar(literal.GetChar(5)))) { value = false; return(literal.Advance(5)); } if (literal.StartsWith("null") && (literal.Length == 4 || !IsValidVarNameChar(literal.GetChar(4)))) { value = JsNull.Value; return(literal.Advance(4)); } if (firstChar.IsOperatorChar()) { if (literal.StartsWith(">=")) { binding = JsGreaterThanEqual.Operand; return(literal.Advance(2)); } if (literal.StartsWith("<=")) { binding = JsLessThanEqual.Operand; return(literal.Advance(2)); } if (literal.StartsWith("!==")) { binding = JsStrictNotEquals.Operand; return(literal.Advance(3)); } if (literal.StartsWith("!=")) { binding = JsNotEquals.Operand; return(literal.Advance(2)); } if (literal.StartsWith("===")) { binding = JsStrictEquals.Operand; return(literal.Advance(3)); } if (literal.StartsWith("==")) { binding = JsEquals.Operand; return(literal.Advance(2)); } if (literal.StartsWith("||")) { binding = JsOr.Operator; return(literal.Advance(2)); } if (literal.StartsWith("&&")) { binding = JsAnd.Operator; return(literal.Advance(2)); } switch (firstChar) { case '>': binding = JsGreaterThan.Operand; return(literal.Advance(1)); case '<': binding = JsLessThan.Operand; return(literal.Advance(1)); case '=': binding = JsAssignment.Operator; return(literal.Advance(1)); case '!': binding = JsNot.Operator; return(literal.Advance(1)); case '+': binding = JsAddition.Operator; return(literal.Advance(1)); case '-': binding = JsSubtraction.Operator; return(literal.Advance(1)); case '*': binding = JsMultiplication.Operator; return(literal.Advance(1)); case '\\': binding = JsDivision.Operator; return(literal.Advance(1)); case '|': binding = JsBitwiseOr.Operator; return(literal.Advance(1)); case '&': binding = JsBitwiseAnd.Operator; return(literal.Advance(1)); default: throw new NotSupportedException($"Invalid Operator found near: '{literal.SubstringWithElipsis(0, 50)}'"); } } // name i = 1; var isExpression = false; var hadWhitespace = false; while (i < literal.Length && IsValidVarNameChar(c = literal.GetChar(i)) || (isExpression = c.IsBindingExpressionChar() || (allowWhitespaceSyntax && c == ':'))) { if (isExpression) { literal = literal.ParseNextExpression(out JsExpression expr); binding = expr; return(literal); } i++; while (i < literal.Length && literal.GetChar(i).IsWhiteSpace()) // advance past whitespace { i++; hadWhitespace = true; } if (hadWhitespace && (i >= literal.Length || !literal.GetChar(i).IsBindingExpressionChar())) { break; } } binding = new JsBinding(literal.Subsegment(0, i).TrimEnd()); return(literal.Advance(i)); }
public static StringSegment ParseNextExpression(this StringSegment literal, out JsExpression binding) { var inDoubleQuotes = false; var inSingleQuotes = false; var inBackTickQuotes = false; var inBrackets = 0; var inBraces = 0; var lastPos = 0; for (var i = 0; i < literal.Length; i++) { var c = literal.GetChar(i); if (c.IsWhiteSpace()) { continue; } if (inDoubleQuotes) { if (c == '"') { inDoubleQuotes = false; } continue; } if (inSingleQuotes) { if (c == '\'') { inSingleQuotes = false; } continue; } if (inBackTickQuotes) { if (c == '`') { inBackTickQuotes = false; } continue; } if (inBrackets > 0) { if (c == '[') { ++inBrackets; } if (c == ']') { --inBrackets; } continue; } if (inBraces > 0) { if (c == '{') { ++inBraces; } if (c == '}') { --inBraces; } continue; } if (c == ':') //whitespace sensitive syntax { // replace everything after ':' up till new line and rewrite as single string to method var endStringPos = literal.IndexOf("\n", i); var endStatementPos = literal.IndexOf("}}", i); if (endStringPos == -1 || (endStatementPos != -1 && endStatementPos < endStringPos)) { endStringPos = endStatementPos; } if (endStringPos == -1) { throw new NotSupportedException($"Whitespace sensitive syntax did not find a '\\n' new line to mark the end of the statement, near '{literal.SubstringWithElipsis(i,50)}'"); } binding = new JsExpression(literal.Subsegment(0, i).Trim()); var originalArgs = literal.Substring(i + 1, endStringPos - i - 1); var rewrittenArgs = "`" + originalArgs.Trim().Replace("{", "{{").Replace("}", "}}").Replace("`", "\\`") + "`)"; ParseArguments(rewrittenArgs.ToStringSegment(), out List <StringSegment> args); binding.Args = args; return(literal.Subsegment(endStringPos)); } if (c == '(') { var pos = i + 1; binding = new JsExpression(literal.Subsegment(0, i).Trim()); literal = ParseArguments(literal.Subsegment(pos), out List <StringSegment> args); binding.Args = args; return(literal.Advance(1)); } switch (c) { case '"': inDoubleQuotes = true; continue; case '\'': inSingleQuotes = true; continue; case '`': inBackTickQuotes = true; continue; case '[': inBrackets++; continue; case '{': inBraces++; continue; } if (!(c.IsValidVarNameChar() || c.IsBindingExpressionChar())) { binding = new JsExpression(literal.Subsegment(lastPos, i - lastPos).Trim()); return(literal.Advance(i)); } } binding = new JsExpression(literal.Subsegment(0, literal.Length)); return(TypeConstants.EmptyStringSegment); }
// ( {args} , {args} ) // ^ public static StringSegment ParseArguments(StringSegment argsString, out List <StringSegment> args) { var to = new List <StringSegment>(); var inDoubleQuotes = false; var inSingleQuotes = false; var inBackTickQuotes = false; var inBrackets = 0; var inParens = 0; var inBraces = 0; var lastPos = 0; for (var i = 0; i < argsString.Length; i++) { var c = argsString.GetChar(i); if (inDoubleQuotes) { if (c == '"') { inDoubleQuotes = false; } continue; } if (inSingleQuotes) { if (c == '\'') { inSingleQuotes = false; } continue; } if (inBackTickQuotes) { if (c == '`') { inBackTickQuotes = false; } continue; } if (inBrackets > 0) { if (c == '[') { ++inBrackets; } if (c == ']') { --inBrackets; } continue; } if (inBraces > 0) { if (c == '{') { ++inBraces; } if (c == '}') { --inBraces; } continue; } if (inParens > 0) { if (c == '(') { ++inParens; } if (c == ')') { --inParens; } continue; } switch (c) { case '"': inDoubleQuotes = true; continue; case '\'': inSingleQuotes = true; continue; case '`': inBackTickQuotes = true; continue; case '[': inBrackets++; continue; case '{': inBraces++; continue; case '(': inParens++; continue; case ',': { var arg = argsString.Subsegment(lastPos, i - lastPos).Trim(); to.Add(arg); lastPos = i + 1; continue; } case ')': { var arg = argsString.Subsegment(lastPos, i - lastPos).Trim(); if (!arg.IsNullOrEmpty()) { to.Add(arg); } args = to; return(argsString.Advance(i)); } } } args = to; return(TypeConstants.EmptyStringSegment); }
public static StringSegment ParseNextExpression(this StringSegment literal, out JsExpression binding) { var inDoubleQuotes = false; var inSingleQuotes = false; var inBackTickQuotes = false; var inBrackets = 0; var inBraces = 0; var lastPos = 0; for (var i = 0; i < literal.Length; i++) { var c = literal.GetChar(i); if (c.IsWhiteSpace()) { continue; } if (inDoubleQuotes) { if (c == '"') { inDoubleQuotes = false; } continue; } if (inSingleQuotes) { if (c == '\'') { inSingleQuotes = false; } continue; } if (inBackTickQuotes) { if (c == '`') { inBackTickQuotes = false; } continue; } if (inBrackets > 0) { if (c == '[') { ++inBrackets; } if (c == ']') { --inBrackets; } continue; } if (inBraces > 0) { if (c == '{') { ++inBraces; } if (c == '}') { --inBraces; } continue; } if (c == '(') { var pos = i + 1; binding = new JsExpression(literal.Subsegment(0, i).Trim()); literal = ParseArguments(literal.Subsegment(pos), out List <StringSegment> args); binding.Args = args; return(literal.Advance(1)); } switch (c) { case '"': inDoubleQuotes = true; continue; case '\'': inSingleQuotes = true; continue; case '`': inBackTickQuotes = true; continue; case '[': inBrackets++; continue; case '{': inBraces++; continue; } if (!(c.IsValidVarNameChar() || c.IsBindingExpressionChar())) { binding = new JsExpression(literal.Subsegment(lastPos, i - lastPos).Trim()); return(literal.Advance(i)); } } binding = new JsExpression(literal.Subsegment(0, literal.Length)); return(TypeConstants.EmptyStringSegment); }
internal static StringSegment ParseIdentifier(this StringSegment literal, out JsToken token) { var i = 0; literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { throw new SyntaxErrorException("Expected identifier before end"); } var c = literal.GetChar(i); if (!c.IsValidVarNameChar()) { throw new SyntaxErrorException($"Expected start of identifier but was '{c}'"); } i++; while (i < literal.Length) { c = literal.GetChar(i); if (IsValidVarNameChar(c)) { i++; } else { break; } } var identifier = literal.Subsegment(0, i).TrimEnd(); literal = literal.Advance(i); if (identifier.Equals("true")) { token = JsLiteral.True; } else if (identifier.Equals("false")) { token = JsLiteral.False; } else if (identifier.Equals("null")) { token = JsNull.Value; } else if (identifier.Equals("and")) { token = JsAnd.Operator; } else if (identifier.Equals("or")) { token = JsOr.Operator; } else { token = new JsIdentifier(identifier); } return(literal); }
public static StringSegment ParseJsToken(this StringSegment literal, out JsToken token, bool filterExpression) { literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { token = null; return(literal); } var c = literal.GetChar(0); if (c == '(') { literal = literal.Advance(1); literal = literal.ParseJsExpression(out var bracketsExpr); literal = literal.AdvancePastWhitespace(); c = literal.GetChar(0); if (c == ')') { literal = literal.Advance(1); token = bracketsExpr; return(literal); } throw new SyntaxErrorException($"Expected ')' but instead found '{c}': {literal.SubstringWithElipsis(0, 50)}"); } token = null; c = (char)0; if (literal.IsNullOrEmpty()) { return(TypeConstants.EmptyStringSegment); } var i = 0; literal = literal.AdvancePastWhitespace(); var firstChar = literal.GetChar(0); if (firstChar == '\'' || firstChar == '"' || firstChar == '`' || firstChar == '′') { i = 1; var hasEscapeChar = false; while (i < literal.Length && ((c = literal.GetChar(i)) != firstChar || literal.GetChar(i - 1) == '\\')) { i++; if (!hasEscapeChar) { hasEscapeChar = c == '\\'; } } if (i >= literal.Length || literal.GetChar(i) != firstChar) { throw new SyntaxErrorException($"Unterminated string literal: {literal}"); } var str = literal.Substring(1, i - 1); token = new JsLiteral(str); if (hasEscapeChar) { var sb = StringBuilderCache.Allocate(); for (var j = 0; j < str.Length; j++) { // strip the back-slash used to escape quote char in strings var ch = str[j]; if (ch != '\\' || (j + 1 >= str.Length || str[j + 1] != firstChar)) { sb.Append(ch); } } token = new JsLiteral(StringBuilderCache.ReturnAndFree(sb)); } return(literal.Advance(i + 1)); } if (firstChar >= '0' && firstChar <= '9') { i = 1; var hasExponent = false; var hasDecimal = false; while (i < literal.Length && IsNumericChar(c = literal.GetChar(i)) || (hasExponent = (c == 'e' || c == 'E'))) { if (c == '.') { hasDecimal = true; } i++; if (hasExponent) { i += 2; // [e+1]0 while (i < literal.Length && IsNumericChar(literal.GetChar(i))) { i++; } break; } } var numLiteral = literal.Subsegment(0, i); //don't convert into ternary to avoid Type coercion if (hasDecimal || hasExponent) { token = new JsLiteral(numLiteral.TryParseDouble(out double d) ? d : default(double)); } else { token = new JsLiteral(numLiteral.ParseSignedInteger()); } return(literal.Advance(i)); } if (firstChar == '{') { var props = new List <JsProperty>(); literal = literal.Advance(1); while (!literal.IsNullOrEmpty()) { literal = literal.AdvancePastWhitespace(); if (literal.GetChar(0) == '}') { literal = literal.Advance(1); break; } literal = literal.ParseJsToken(out var mapKeyToken); if (!(mapKeyToken is JsLiteral) && !(mapKeyToken is JsIdentifier)) { throw new SyntaxErrorException($"'{mapKeyToken}' is not a valid Object key, expected literal or identifier."); } JsToken mapValueToken; bool shorthand = false; literal = literal.AdvancePastWhitespace(); if (literal.Length > 0 && literal.GetChar(0) == ':') { literal = literal.Advance(1); literal = literal.ParseJsExpression(out mapValueToken); } else { shorthand = true; if (literal.Length == 0 || (c = literal.GetChar(0)) != ',' && c != '}') { throw new SyntaxErrorException($"Unterminated object literal near: {literal.SubstringWithElipsis(0, 50)}"); } mapValueToken = mapKeyToken; } props.Add(new JsProperty(mapKeyToken, mapValueToken, shorthand)); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { break; } if (literal.GetChar(0) == '}') { literal = literal.Advance(1); break; } literal = literal.AdvancePastChar(','); literal = literal.AdvancePastWhitespace(); } token = new JsObjectExpression(props); return(literal); } if (firstChar == '[') { literal = literal.ParseArguments(out var elements, termination: ']'); token = new JsArrayExpression(elements); return(literal); } if (firstChar.IsOperatorChar()) { if (literal.StartsWith(">=")) { token = JsGreaterThanEqual.Operator; return(literal.Advance(2)); } if (literal.StartsWith("<=")) { token = JsLessThanEqual.Operator; return(literal.Advance(2)); } if (literal.StartsWith("!==")) { token = JsStrictNotEquals.Operator; return(literal.Advance(3)); } if (literal.StartsWith("!=")) { token = JsNotEquals.Operator; return(literal.Advance(2)); } if (literal.StartsWith("===")) { token = JsStrictEquals.Operator; return(literal.Advance(3)); } if (literal.StartsWith("==")) { token = JsEquals.Operator; return(literal.Advance(2)); } if (literal.StartsWith("||")) { token = JsOr.Operator; return(literal.Advance(2)); } if (literal.StartsWith("&&")) { token = JsAnd.Operator; return(literal.Advance(2)); } if (literal.StartsWith("<<")) { token = JsBitwiseLeftShift.Operator; return(literal.Advance(2)); } if (literal.StartsWith(">>")) { token = JsBitwiseRightShift.Operator; return(literal.Advance(2)); } switch (firstChar) { case '>': token = JsGreaterThan.Operator; return(literal.Advance(1)); case '<': token = JsLessThan.Operator; return(literal.Advance(1)); case '=': token = JsAssignment.Operator; return(literal.Advance(1)); case '!': token = JsNot.Operator; return(literal.Advance(1)); case '+': token = JsAddition.Operator; return(literal.Advance(1)); case '-': token = JsSubtraction.Operator; return(literal.Advance(1)); case '*': token = JsMultiplication.Operator; return(literal.Advance(1)); case '/': token = JsDivision.Operator; return(literal.Advance(1)); case '&': token = JsBitwiseAnd.Operator; return(literal.Advance(1)); case '|': token = JsBitwiseOr.Operator; return(literal.Advance(1)); case '^': token = JsBitwiseXOr.Operator; return(literal.Advance(1)); case '%': token = JsMod.Operator; return(literal.Advance(1)); default: throw new SyntaxErrorException($"Invalid Operator found near: '{literal.SubstringWithElipsis(0, 50)}'"); } } // identifier var preIdentifierLiteral = literal; literal = literal.ParseIdentifier(out var node); literal = literal.AdvancePastWhitespace(); if (!literal.IsNullOrEmpty()) { c = literal.GetChar(i); if (c == '.' || c == '[') { while (true) { literal = literal.Advance(1); if (c == '.') { literal = literal.AdvancePastWhitespace(); literal = literal.ParseIdentifier(out var property); node = new JsMemberExpression(node, property, computed: false); } else if (c == '[') { literal = literal.AdvancePastWhitespace(); literal = literal.ParseJsExpression(out var property); node = new JsMemberExpression(node, property, computed: true); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty() || literal.GetChar(0) != ']') { throw new SyntaxErrorException($"Expected ']' but was '{literal.GetChar(0)}'"); } literal = literal.Advance(1); } literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrWhiteSpace()) { break; } c = literal.GetChar(0); if (c == '(') { throw new SyntaxErrorException("Call expression found on member expression. Only filters can be invoked."); } if (!(c == '.' || c == '[')) { break; } } } else if (c == '(' || (filterExpression && c == ':')) { literal = preIdentifierLiteral.ParseJsCallExpression(out var callExpr, filterExpression: filterExpression); token = callExpr; return(literal); } } token = node; return(literal); }
internal static StringSegment ParseArguments(this StringSegment literal, out List <JsToken> arguments, char termination) { arguments = new List <JsToken>(); while (!literal.IsNullOrEmpty()) { JsToken listValue; literal = literal.AdvancePastWhitespace(); if (literal.GetChar(0) == termination) { literal = literal.Advance(1); break; } if (literal.StartsWith("...")) { literal = literal.Advance(3); literal = literal.ParseJsExpression(out listValue); if (!(listValue is JsIdentifier) && !(listValue is JsArrayExpression)) { throw new SyntaxErrorException($"Spread operator expected array but instead found {listValue.DebugToken()}"); } listValue = new JsSpreadElement(listValue); } else { literal = literal.ParseJsExpression(out listValue); } arguments.Add(listValue); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { break; } if (literal.GetChar(0) == termination) { literal = literal.Advance(1); break; } literal = literal.AdvancePastWhitespace(); var c = literal.SafeGetChar(0); if (c.IsEnd() || c == termination) { literal = literal.Advance(1); break; } if (c != ',') { throw new SyntaxErrorException($"Unterminated arguments expression near: {literal.DebugLiteral()}"); } literal = literal.Advance(1); literal = literal.AdvancePastWhitespace(); } literal = literal.AdvancePastWhitespace(); return(literal); }
public static StringSegment ParseNextToken(this StringSegment literal, out object value, out JsBinding binding) { binding = null; value = null; var c = (char)0; if (literal.IsNullOrEmpty()) { return(TypeConstants.EmptyStringSegment); } var i = 0; literal = literal.AdvancePastWhitespace(); var firstChar = literal.GetChar(0); if (firstChar == '\'' || firstChar == '"') { i = 1; while (i < literal.Length && (literal.GetChar(i) != firstChar || literal.GetChar(i - 1) == '\\')) { i++; } if (i >= literal.Length || literal.GetChar(i) != firstChar) { throw new ArgumentException($"Unterminated string literal: {literal}"); } value = literal.Substring(1, i - 1); return(literal.Advance(i)); } if (firstChar >= '0' && firstChar <= '9' || firstChar == '-' || firstChar == '+') { i = 1; var hasExponent = false; var hasDecimal = false; while (i < literal.Length && IsValidNumericChar(c = literal.GetChar(i)) || (hasExponent = (c == 'e' || c == 'E'))) { if (c == '.') { hasDecimal = true; } i++; if (hasExponent) { i += 2; // [e+1]0 while (i < literal.Length && IsValidNumericChar(literal.GetChar(i))) { i++; } break; } } var numLiteral = literal.Subsegment(0, i); //don't convert into ternary to avoid Type coercion if (hasDecimal || hasExponent) { value = numLiteral.TryParseDouble(out double d) ? d : default(double); } else { value = numLiteral.ParseSignedInteger(); } return(literal.Advance(i)); } if (firstChar == '{') { var map = new Dictionary <string, object>(); literal = literal.Advance(1); while (!literal.IsNullOrEmpty()) { literal = literal.ParseNextToken(out object mapKeyString, out JsBinding mapKeyVar); if (mapKeyVar is JsExpression) { throw new NotSupportedException($"JsExpression '{mapKeyVar?.Binding}' is not a valid Object key."); } var mapKey = mapKeyVar != null ? mapKeyVar.Binding.Value : (string)mapKeyString; if (mapKey != null) { literal = literal.AdvancePastChar(':'); literal = literal.ParseNextToken(out object mapValue, out JsBinding mapValueBinding); map[mapKey] = mapValue ?? mapValueBinding; } literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { break; } if (literal.GetChar(0) == '}') { literal = literal.Advance(1); break; } literal = literal.AdvancePastChar(','); literal = literal.AdvancePastWhitespace(); } value = map; return(literal); } if (firstChar == '[') { var list = new List <object>(); literal = literal.Advance(1); while (!literal.IsNullOrEmpty() && literal.GetChar(0) != ']') { literal = literal.ParseNextToken(out object mapValue, out JsBinding mapVarRef); list.Add(mapVarRef ?? mapValue); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { break; } if (literal.GetChar(0) == ']') { literal = literal.Advance(1); break; } literal = literal.AdvancePastChar(','); literal = literal.AdvancePastWhitespace(); } value = list; return(literal); } if (literal.StartsWith("true") && (literal.Length == 4 || !IsValidVarNameChar(literal.GetChar(4)))) { value = true; return(literal.Advance(4)); } if (literal.StartsWith("false") && (literal.Length == 5 || !IsValidVarNameChar(literal.GetChar(5)))) { value = false; return(literal.Advance(5)); } if (literal.StartsWith("null") && (literal.Length == 4 || !IsValidVarNameChar(literal.GetChar(4)))) { value = JsNull.Value; return(literal.Advance(4)); } // name i = 1; var isExpression = false; while (i < literal.Length && IsValidVarNameChar(c = literal.GetChar(i)) || (isExpression = (c == '.' || c == '(' || c == '['))) { if (isExpression) { binding = literal.ParseJsExpression(out int pos).FirstOrDefault(); return(literal.Advance(pos)); } i++; while (i < literal.Length && literal.GetChar(i).IsWhiteSpace()) // advance past whitespace { i++; } } binding = new JsBinding(literal.Subsegment(0, i).TrimEnd()); return(literal.Advance(i)); }