Exemplo n.º 1
0
 public void Copy(ProcessOutcome other)
 {
     Continue    = other.Continue;
     Leave       = other.Leave;
     Return      = other.Return;
     ReturnValue = other.ReturnValue;
 }
Exemplo n.º 2
0
        /// <summary>
        /// Process a section of the script, yielding an outcome
        /// </summary>
        /// <param name="startLine">What line of the script to start on?</param>
        /// <param name="endLine">What line of the script to end with?</param>
        /// <param name="outcome">Outcome object filled in by processing the script</param>
        /// <param name="indentLevel">Indent level for HTML output</param>
        /// <returns>Return value from a function call, or null</returns>
        public async Task <object> ProcessAsync(int startLine, int endLine, ProcessOutcome outcome, int indentLevel, int callDepth)
        {
            for (int l = startLine; l <= endLine; ++l)
            {
                string line = m_lines[l].Trim();
                if (line.Length == 0) // skip blank lines
                {
                    continue;
                }

                char first = line[0];
#if !DEBUG
                try
#endif
                {
                    if (line == "/*")
                    {
                        ++l;
                        while (m_lines[l].Trim() != "*/")
                        {
                            ++l;
                            if (l >= m_lines.Length)
                            {
                                throw new ScriptException("Unfinished block comment");
                            }
                        }
                    }
                    else if (first == '!')
                    {
                        // No op
                    }
                    else if (line == "{>>")
                    {
                        ++l;
                        while (m_lines[l].Trim() != ">>}")
                        {
                            m_writer.WriteLine(m_lines[l]);
                            ++l;
                            if (l >= m_lines.Length)
                            {
                                throw new ScriptException("Unfinished block print");
                            }
                        }
                    }
                    else if (line.StartsWith(">>", StringComparison.Ordinal))
                    {
                        string valueStr = line.Substring(">>".Length).Trim();
                        if (indentLevel > 0)
                        {
                            valueStr = new string('\t', indentLevel) + valueStr;
                        }
                        m_writer.WriteLine(valueStr);
                    }
                    else if (first == '>')
                    {
                        string valueStr = line.Substring(1).Trim();
                        string answer   = Utils.ToString(await EvaluateValueAsync(valueStr, indentLevel, callDepth).ConfigureAwait(false));
                        if (indentLevel > 0)
                        {
                            answer = new string('\t', indentLevel) + answer;
                        }
                        m_writer.WriteLine(answer);
                    }
                    else if (first == '$')
                    {
                        line = line.Substring(1);
                        int equalsIndex = line.IndexOf('=');

                        string nameStr  = line.Substring(0, equalsIndex).Trim();
                        string valueStr = line.Substring(equalsIndex + 1).Trim();

                        object answer = await EvaluateValueAsync(valueStr, indentLevel, callDepth).ConfigureAwait(false);

                        m_symbols.Set(nameStr, answer);
                    }
                    else if (first == '&')
                    {
                        line = line.Substring(1);
                        int equalsIndex = line.IndexOf('=');

                        string nameStr  = line.Substring(0, equalsIndex).Trim();
                        string valueStr = line.Substring(equalsIndex + 1).Trim();

                        object answer = await EvaluateValueAsync(valueStr, indentLevel, callDepth).ConfigureAwait(false);

                        m_symbols.Assign(nameStr, answer);
                    }
                    else if (first == 'O')
                    {
                        int loopEnd   = FindMatchingEnd(m_lines, l, endLine);
                        int loopStart = l;
                        l = loopEnd;
                        using (var stacker = new SymbolStacker(m_symbols))
                        {
                            while (true)
                            {
                                var ourOutcome = new ProcessOutcome();
                                await ProcessAsync(loopStart + 1, loopEnd - 1, ourOutcome, indentLevel, callDepth + 1).ConfigureAwait(false);

                                if (ourOutcome.Return)
                                {
                                    outcome.Return      = true;
                                    outcome.ReturnValue = ourOutcome.ReturnValue;
                                    return(ourOutcome.ReturnValue);
                                }
                                else if (ourOutcome.Continue)
                                {
                                    continue;
                                }
                                else if (ourOutcome.Leave)
                                {
                                    break;
                                }
                            }
                        }
                    }
                    else if (first == '{')
                    {
                        int loopEnd   = FindMatchingEnd(m_lines, l, endLine);
                        int loopStart = l;
                        l = loopEnd;
                        using (var stacker = new SymbolStacker(m_symbols))
                        {
                            var ourOutcome = new ProcessOutcome();
                            await ProcessAsync(loopStart + 1, loopEnd - 1, ourOutcome, indentLevel, callDepth + 1).ConfigureAwait(false);

                            if (ourOutcome.Return)
                            {
                                outcome.Return      = true;
                                outcome.ReturnValue = ourOutcome.ReturnValue;
                                return(ourOutcome.ReturnValue);
                            }
                            else if (ourOutcome.Continue)
                            {
                                outcome.Continue = true;
                                return(null);
                            }
                            else if (ourOutcome.Leave)
                            {
                                outcome.Leave = true;
                                return(null);
                            }
                        }
                    }
                    else if (line == "<-" || line == "->")
                    {
                        outcome.Return = true;
                        return(null);
                    }
                    else if (line.StartsWith("<- ", StringComparison.Ordinal) ||
                             line.StartsWith("-> ", StringComparison.Ordinal))
                    {
                        int    space  = line.IndexOf(' ');
                        string expStr = line.Substring(space + 1);
                        outcome.ReturnValue = await EvaluateValueAsync(expStr, indentLevel, callDepth).ConfigureAwait(false);

                        outcome.Return = true;
                        return(outcome.ReturnValue);
                    }
                    else if (line.StartsWith("<{", StringComparison.Ordinal))
                    {
                        line = line.Substring("<{".Length).Trim();

                        int firstSpace = line.IndexOf(' ');

                        string name;
                        if (firstSpace < 0)
                        {
                            name = line.Trim();
                        }
                        else
                        {
                            name = line.Substring(0, firstSpace).Trim();
                        }
                        Names.ValidateName(name);

                        index settings = null;
                        if (firstSpace > 0)
                        {
                            string expression = line.Substring(firstSpace + 1).Trim();
                            if (expression.Length > 0)
                            {
                                object expValue = await EvaluateValueAsync(expression, indentLevel, callDepth).ConfigureAwait(false);

                                if (!(expValue is index))
                                {
                                    throw new ScriptException("Invalid expression for block tag parameters, must be index");
                                }
                                settings = (index)expValue;
                            }
                        }

                        string scriptName = "", formAction = "";
                        if (name.Equals("form", StringComparison.OrdinalIgnoreCase))
                        {
                            object scriptNameObj = null;
                            if (settings == null || !settings.TryGetValue("script", out scriptNameObj))
                            {
                                scriptNameObj = m_scriptName;
                            }
                            scriptName = scriptNameObj != null?scriptNameObj.ToString() : "";

                            object formActionObj = null;
                            if (settings == null || !settings.TryGetValue("action", out formActionObj))
                            {
                                formActionObj = m_formAction;
                            }
                            formAction = formActionObj != null?formActionObj.ToString() : "";
                        }

                        int loopEnd   = FindMatchingEnd(m_lines, l, endLine);
                        int loopStart = l;
                        l = loopEnd;

                        string tag = "<" + name;
                        if (settings != null)
                        {
                            foreach (var kvp in settings.Entries)
                            {
                                string key = (string)kvp.Key;
                                if
                                (
                                    !key.Equals("script", StringComparison.OrdinalIgnoreCase)
                                    &&
                                    !key.Equals("action", StringComparison.OrdinalIgnoreCase)
                                )
                                {
                                    tag
                                        += " " + kvp.Key
                                           + "=\"" + Utils.ObjectToHtmlAttribute(kvp.Value) + "\"";
                                }
                            }
                        }

                        if (!string.IsNullOrWhiteSpace(formAction))
                        {
                            tag += " action=\"" + Utils.ObjectToHtmlAttribute(formAction) + "\"";
                        }

                        tag += ">";
                        if (indentLevel > 0)
                        {
                            tag = new string('\t', indentLevel) + tag;
                        }
                        m_writer.WriteLine(tag);

                        if (!string.IsNullOrWhiteSpace(scriptName))
                        {
                            string hidden = $"<input type=hidden name=script value=\"{Utils.ObjectToHtmlAttribute(scriptName)}\"/>";
                            if (indentLevel >= 0)
                            {
                                hidden = new string('\t', indentLevel + 1) + hidden;
                            }
                            m_writer.WriteLine(hidden);
                        }

                        try
                        {
                            using (var stacker = new SymbolStacker(m_symbols))
                            {
                                var ourOutcome = new ProcessOutcome();
                                await ProcessAsync(loopStart + 1, loopEnd - 1, ourOutcome, indentLevel + 1, callDepth + 1).ConfigureAwait(false);

                                outcome.Copy(ourOutcome);
                                if (ourOutcome.Return)
                                {
                                    return(ourOutcome.ReturnValue);
                                }
                            }
                        }
                        finally
                        {
                            string closingTag = "</" + name + ">";
                            if (indentLevel > 0)
                            {
                                closingTag = new string('\t', indentLevel) + closingTag;
                            }
                            m_writer.WriteLine(closingTag);
                        }
                    }
                    else if (first == '<')
                    {
                        line = line.Substring(1).Trim();

                        int firstSpace = line.IndexOf(' ');

                        string name;
                        if (firstSpace < 0)
                        {
                            name = line;
                        }
                        else
                        {
                            name = line.Substring(0, firstSpace).Trim();
                        }
                        Names.ValidateName(name);

                        index settings = null;
                        if (firstSpace > 0)
                        {
                            string expression = line.Substring(firstSpace + 1).Trim();
                            if (expression.Length > 0)
                            {
                                object expValue = await EvaluateValueAsync(expression, indentLevel, callDepth).ConfigureAwait(false);

                                if (!(expValue is index))
                                {
                                    throw new ScriptException("Invalid expression for blocktag parameters, must be index");
                                }
                                settings = (index)expValue;
                            }
                        }

                        if (settings != null && settings.ContainsKey("inner"))
                        {
                            string inner = settings["inner"].ToString();
                            string tag   = "<" + name;
                            foreach (var kvp in settings.Entries)
                            {
                                if (kvp.Key.ToString() == "inner")
                                {
                                    continue;
                                }
                                tag += " " + kvp.Key + "=\"" + Utils.ObjectToHtmlAttribute(kvp.Value) + "\"";
                            }
                            tag += ">" + inner + "</" + name + ">";
                            if (indentLevel > 0)
                            {
                                tag = new string('\t', indentLevel) + tag;
                            }
                            m_writer.WriteLine(tag);
                        }
                        else
                        {
                            string tag = "<" + name;
                            if (settings != null)
                            {
                                foreach (var kvp in settings.Entries)
                                {
                                    tag += " " + kvp.Key + "=\"" + Utils.ObjectToHtmlAttribute(kvp.Value) + "\"";
                                }
                            }
                            tag += "/>";
                            if (indentLevel > 0)
                            {
                                tag = new string('\t', indentLevel) + tag;
                            }
                            m_writer.WriteLine(tag);
                        }
                    }
                    else if (first == '?')
                    {
                        bool seenQuestion  = false;
                        bool seenPlainElse = false;
                        var  markers       = FindElses(m_lines, l, endLine);
                        int  endMarker     = markers[markers.Count - 1];
                        l = endMarker;
                        for (int m = 0; m < markers.Count - 1; ++m)
                        {
                            int    marker     = markers[m];
                            int    nextMarker = markers[Math.Min(m + 1, markers.Count - 1)];
                            string markerLine = m_lines[marker].Trim();

                            object answer;
                            int    spaceIndex = markerLine.IndexOf(' ');
                            if (spaceIndex < 0)
                            {
                                if (seenPlainElse)
                                {
                                    throw new ScriptException("Already seen <> statement");
                                }

                                if (!seenQuestion)
                                {
                                    throw new ScriptException("No ? statement before <> statement");
                                }

                                seenPlainElse = true;
                                answer        = true;
                            }
                            else
                            {
                                seenQuestion = true;

                                if (seenPlainElse)
                                {
                                    throw new ScriptException("Already seen <> statement");
                                }

                                string criteria = markerLine.Substring(spaceIndex + 1);
                                answer = await EvaluateValueAsync(criteria, indentLevel, callDepth).ConfigureAwait(false);

                                if (!(answer is bool))
                                {
                                    throw new ScriptException("? expression does not evaluate to true or false");
                                }
                            }

                            if (!Utils.BoolCast(answer))
                            {
                                continue;
                            }

                            using (var stacker = new SymbolStacker(m_symbols))
                            {
                                var ourOutcome = new ProcessOutcome();
                                await ProcessAsync(marker + 1, nextMarker - 1, ourOutcome, indentLevel, callDepth + 1).ConfigureAwait(false);

                                outcome.Copy(ourOutcome);
                                if (ourOutcome.Return)
                                {
                                    return(ourOutcome.ReturnValue);
                                }
                                else if (ourOutcome.Continue)
                                {
                                    return(null);
                                }
                                else if (ourOutcome.Leave)
                                {
                                    return(null);
                                }
                            }
                            break;
                        }
                    }
                    else if (first == '@')
                    {
                        int firstSpace = line.IndexOf(' ');
                        int nextSpace  = line.IndexOf(' ', firstSpace + 1);
                        if (nextSpace < 0)
                        {
                            throw new ScriptException("@ statement lacks : part");
                        }
                        int thirdSpace = line.IndexOf(' ', nextSpace + 1);
                        if (thirdSpace < 0)
                        {
                            throw new ScriptException("@ statement lacks collection expression");
                        }

                        string label = line.Substring(firstSpace, nextSpace - firstSpace).Trim();
                        Names.ValidateName(label);

                        string expression = line.Substring(thirdSpace + 1);

                        int loopEnd   = FindMatchingEnd(m_lines, l, endLine);
                        int loopStart = l;
                        l = loopEnd;

                        IEnumerable <object> enumerable = null;
                        {
                            object answer = await EvaluateValueAsync(expression, indentLevel, callDepth).ConfigureAwait(false);

                            if (answer is string)
                            {
                                enumerable = ((string)answer).ToCharArray().Select(c => (object)c.ToString());
                            }
                            else if (answer is list)
                            {
                                enumerable = (list)answer;
                            }
                            else if (answer is index)
                            {
                                enumerable = new List <object>(((index)answer).Keys);
                            }
                            else
                            {
                                throw new ScriptException("The @ statement only works with strings, lists, and indexes");
                            }
                        }

                        using (var outerStacker = new SymbolStacker(m_symbols))
                        {
                            m_symbols.Set(label, null);
                            foreach (object val in enumerable)
                            {
                                using (var innerStacker = new SymbolStacker(m_symbols))
                                {
                                    m_symbols.Assign(label, val);

                                    var ourOutcome = new ProcessOutcome();
                                    await ProcessAsync(loopStart + 1, loopEnd - 1, ourOutcome, indentLevel, callDepth + 1).ConfigureAwait(false);

                                    if (ourOutcome.Return)
                                    {
                                        outcome.Copy(ourOutcome);
                                        return(ourOutcome.ReturnValue);
                                    }
                                    else if (ourOutcome.Continue)
                                    {
                                        continue;
                                    }
                                    else if (ourOutcome.Leave)
                                    {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    else if (first == '#')
                    {
                        int firstSpace = line.IndexOf(' ');
                        int nextSpace  = line.IndexOf(' ', firstSpace + 1);
                        if (nextSpace < 0)
                        {
                            throw new ScriptException("# statement lacks counter variable");
                        }

                        string label = line.Substring(firstSpace, nextSpace - firstSpace).Trim();
                        Names.ValidateName(label);

                        int thirdSpace = line.IndexOf(' ', nextSpace + 1);
                        if (thirdSpace < 0)
                        {
                            throw new ScriptException("# statement lacks from part");
                        }

                        string from = line.Substring(nextSpace, thirdSpace - nextSpace).Trim();
                        if (from != ":")
                        {
                            throw new ScriptException("# statement invalid : part");
                        }

                        string theRest = line.Substring(thirdSpace + 1);
                        string fromExpStr = null, toExpStr = null;
                        int    parenCount = 0;
                        bool   inString   = false;
                        for (int f = 0; f < theRest.Length - " -> ".Length; ++f)
                        {
                            char c = theRest[f];
                            if (c == '\"' && (f == 0 || theRest[f - 1] != '\\'))
                            {
                                inString = !inString;
                            }

                            if (!inString)
                            {
                                if (c == '(')
                                {
                                    ++parenCount;
                                }
                                else if (c == ')')
                                {
                                    --parenCount;
                                }
                            }

                            if (!inString && parenCount == 0)
                            {
                                if (theRest.Substring(f).StartsWith(" -> ", StringComparison.Ordinal))
                                {
                                    fromExpStr = theRest.Substring(0, f).Trim();
                                    toExpStr   = theRest.Substring(f + " -> ".Length).Trim();
                                    break;
                                }
                            }
                        }

                        object fromValue = await EvaluateValueAsync(fromExpStr, indentLevel, callDepth).ConfigureAwait(false);

                        if (!(fromValue is double) || Convert.ToInt64(fromValue) != (double)fromValue)
                        {
                            throw new ScriptException("Invalid from value: " + fromValue);
                        }

                        object toValue = await EvaluateValueAsync(toExpStr, indentLevel, callDepth).ConfigureAwait(false);

                        if (!(toValue is double) || Convert.ToInt64(toValue) != (double)toValue)
                        {
                            throw new ScriptException("Invalid to value: " + toValue);
                        }

                        long fromIdx = Convert.ToInt64(fromValue);
                        long toIdx   = Convert.ToInt64(toValue);

                        int loopEnd   = FindMatchingEnd(m_lines, l, endLine);
                        int loopStart = l;
                        l = loopEnd;

                        using (var outerStacker = new SymbolStacker(m_symbols))
                        {
                            m_symbols.Set(label, null);
                            if (fromIdx <= toIdx)
                            {
                                for (long i = fromIdx; i <= toIdx; ++i)
                                {
                                    m_symbols.Assign(label, (double)i);
                                    var ourOutcome = new ProcessOutcome();
                                    await ProcessAsync(loopStart + 1, loopEnd - 1, ourOutcome, indentLevel, callDepth + 1).ConfigureAwait(false);

                                    if (ourOutcome.Return)
                                    {
                                        outcome.Copy(ourOutcome);
                                        return(ourOutcome.ReturnValue);
                                    }
                                    else if (ourOutcome.Continue)
                                    {
                                        continue;
                                    }
                                    else if (ourOutcome.Leave)
                                    {
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                for (long i = fromIdx; i >= toIdx; --i)
                                {
                                    m_symbols.Assign(label, (double)i);
                                    var ourOutcome = new ProcessOutcome();
                                    await ProcessAsync(loopStart + 1, loopEnd - 1, ourOutcome, indentLevel, callDepth + 1).ConfigureAwait(false);

                                    if (ourOutcome.Return)
                                    {
                                        outcome.Copy(ourOutcome);
                                        return(ourOutcome.ReturnValue);
                                    }
                                    else if (ourOutcome.Continue)
                                    {
                                        continue;
                                    }
                                    else if (ourOutcome.Leave)
                                    {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    else if (first == 'f')
                    {
                        if (callDepth != 0)
                        {
                            throw new ScriptException("Functions can only be defined at the top level, not within anything else");
                        }

                        int loopEnd   = FindMatchingEnd(m_lines, l, endLine);
                        int loopStart = l;
                        l = loopEnd;
                    }
                    else if (first == '^')
                    {
                        outcome.Continue = true;
                        return(null);
                    }
                    else if (first == 'v')
                    {
                        outcome.Leave = true;
                        return(null);
                    }
                    // a side-effect perhaps?  like some list.add or msdb.define...?
                    else if (first == '*')
                    {
                        string valueStr = line.Substring(1).Trim();
                        await EvaluateValueAsync(valueStr, indentLevel, callDepth).ConfigureAwait(false);
                    }
                    else
                    {
                        throw new ScriptException("Invalid statement: " + line);
                    }
                }
#if !DEBUG
                catch (Exception exp)
                {
                    HandleException(exp, line, l);
                }
#endif
            }
            return(null);
        }