示例#1
0
        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);
        }
示例#2
0
        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);
        }
示例#3
0
        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);
        }
示例#4
0
        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);
        }
示例#5
0
        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));
        }
示例#6
0
        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);
        }
示例#7
0
        // ( {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);
        }
示例#8
0
        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);
        }
示例#9
0
        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);
        }
示例#10
0
        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);
        }
示例#11
0
        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);
        }
示例#12
0
        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));
        }