public async Task <object> CallFunctionAsync(string name, list paramList) { Function userfunc = null; IScriptContextFunction scriptCtxtFunction = null; if ( !m_userFunctions.TryGetValue(name, out userfunc) && !m_scriptContextFunctions.TryGetValue(name, out scriptCtxtFunction) ) { throw new ScriptException("Function not found: " + name); } if (userfunc != null) { if (paramList.Count != userfunc.ParamNames.Count) { throw new ScriptException ( "Function " + name + " takes " + userfunc.ParamNames.Count + " paramters, " + "not " + paramList.Count + ": " + string.Join(", ", userfunc.ParamNames) ); } using (var smacker = new SymbolSmacker(m_symbols)) using (var stacker = new SymbolStacker(m_symbols)) { for (int p = 0; p < userfunc.ParamNames.Count; ++p) { m_symbols.Set(userfunc.ParamNames[p], paramList[p]); } object returnValue = await ProcessAsync ( userfunc.StartIndex, userfunc.EndIndex, new ProcessOutcome(), m_tempIndentLevel, m_tempCallDepth + 1 ).ConfigureAwait(false); return(returnValue); } } else { object returnValue = await scriptCtxtFunction.CallAsync ( m_scriptContext, paramList ).ConfigureAwait(false); return(returnValue); } }
public void TestSymbolSmacker() { var table = new SymbolTable(); table.Set("foo", "bar"); using (SymbolStacker stacker1 = new SymbolStacker(table)) { table.Set("blet", "monkey"); using (SymbolSmacker smacker = new SymbolSmacker(table)) using (SymbolStacker stacker2 = new SymbolStacker(table)) { Assert.IsTrue(table.Contains("foo")); Assert.IsFalse(table.Contains("blet")); table.Set("something", "else"); } Assert.IsTrue(table.Contains("blet")); Assert.IsFalse(table.Contains("something")); } Assert.IsTrue(table.Contains("foo")); Assert.IsFalse(table.Contains("blet")); Assert.IsFalse(table.Contains("something")); }
/// <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); }