diff --git a/src/CSnakes.Runtime.Tests/Python/RunTests.cs b/src/CSnakes.Runtime.Tests/Python/RunTests.cs index 9b42fcbd..47464144 100644 --- a/src/CSnakes.Runtime.Tests/Python/RunTests.cs +++ b/src/CSnakes.Runtime.Tests/Python/RunTests.cs @@ -6,14 +6,14 @@ public class RunTests : RuntimeTestBase [Fact] public void TestSimpleString() { - using var result = env.Execute("1+1"); + using var result = env.ExecuteExpression("1+1"); Assert.Equal("2", result.ToString()); } [Fact] public void TestBadString() { - Assert.Throws(() => env.Execute("1+")); + Assert.Throws(() => env.ExecuteExpression("1+")); } [Fact] @@ -23,7 +23,7 @@ public void TestSimpleStringWithLocals() { ["a"] = PyObject.From(101) }; - using var result = env.Execute("a+1", locals); + using var result = env.ExecuteExpression("a+1", locals); Assert.Equal("102", result.ToString()); } @@ -38,7 +38,26 @@ public void TestSimpleStringWithLocalsAndGlobals() { ["b"] = PyObject.From(100) }; - using var result = env.Execute("a+b+1", locals, globals); + using var result = env.ExecuteExpression("a+b+1", locals, globals); Assert.Equal("202", result.ToString()); } + + [Fact] + public void TestMultilineInput() + { + var c = """ +a = 101 +b = c + a +"""; + var locals = new Dictionary + { + ["c"] = PyObject.From(101) + }; + var globals = new Dictionary + { + ["d"] = PyObject.From(100) + }; + using var result = env.Execute(c, locals, globals); + Assert.Equal("None", result.ToString()); + } } diff --git a/src/CSnakes.Runtime/IPythonEnvironment.cs b/src/CSnakes.Runtime/IPythonEnvironment.cs index 293461db..624c1862 100644 --- a/src/CSnakes.Runtime/IPythonEnvironment.cs +++ b/src/CSnakes.Runtime/IPythonEnvironment.cs @@ -17,8 +17,4 @@ public string Version public bool IsDisposed(); public ILogger Logger { get; } - - public PyObject Execute(string code); - public PyObject Execute(string code, IDictionary locals); - public PyObject Execute(string code, IDictionary globals, IDictionary locals); } diff --git a/src/CSnakes.Runtime/PythonEnvironment.cs b/src/CSnakes.Runtime/PythonEnvironment.cs index eafe7d51..9467acb6 100644 --- a/src/CSnakes.Runtime/PythonEnvironment.cs +++ b/src/CSnakes.Runtime/PythonEnvironment.cs @@ -145,34 +145,4 @@ public bool IsDisposed() { return disposedValue; } - - public PyObject Execute(string code) - { - using (GIL.Acquire()) - { - using var globals = PyObject.Create(CPythonAPI.PyDict_New()); - using var locals = PyObject.Create(CPythonAPI.PyDict_New()); - return CPythonAPI.PyRun_String(code, CPythonAPI.InputType.Py_eval_input, globals, locals); - } - } - - public PyObject Execute(string code, IDictionary locals) - { - using (GIL.Acquire()) - { - using var localsPyDict = PyObject.From>(locals); - using var globalsPyDict = PyObject.Create(CPythonAPI.PyDict_New()); - return CPythonAPI.PyRun_String(code, CPythonAPI.InputType.Py_eval_input, globalsPyDict, localsPyDict); - } - } - - public PyObject Execute(string code, IDictionary globals, IDictionary locals) - { - using (GIL.Acquire()) - { - using var localsPyDict = PyObject.From>(locals); - using var globalsPyDict = PyObject.From>(globals); - return CPythonAPI.PyRun_String(code, CPythonAPI.InputType.Py_eval_input, globalsPyDict, localsPyDict); - } - } } diff --git a/src/CSnakes.Runtime/PythonRunString.cs b/src/CSnakes.Runtime/PythonRunString.cs new file mode 100644 index 00000000..cfde30d9 --- /dev/null +++ b/src/CSnakes.Runtime/PythonRunString.cs @@ -0,0 +1,75 @@ + +using CSnakes.Runtime.CPython; +using CSnakes.Runtime.Python; + +namespace CSnakes.Runtime; +public static class PythonRunString +{ + /// + /// Execute a single expression in Python and return the result, + /// e.g. `1 + 1` or `len([1, 2, 3])` + /// + /// The Python code + /// The resulting Python object + public static PyObject ExecuteExpression(this IPythonEnvironment env, string code) + { + using (GIL.Acquire()) + { + using var globals = PyObject.Create(CPythonAPI.PyDict_New()); + using var locals = PyObject.Create(CPythonAPI.PyDict_New()); + return CPythonAPI.PyRun_String(code, CPythonAPI.InputType.Py_eval_input, globals, locals); + } + } + + /// + /// Execute a single expression in Python and return the result, with locals + /// e.g. `1 + b` + /// + /// The Python code + /// A dictionary of local variables + /// The resulting Python object + public static PyObject ExecuteExpression(this IPythonEnvironment env, string code, IDictionary locals) + { + using (GIL.Acquire()) + { + using var localsPyDict = PyObject.From(locals); + using var globalsPyDict = PyObject.Create(CPythonAPI.PyDict_New()); + return CPythonAPI.PyRun_String(code, CPythonAPI.InputType.Py_eval_input, globalsPyDict, localsPyDict); + } + } + + /// + /// Execute a single expression in Python and return the result, with locals + /// e.g. `1 + b` + /// + /// The Python code + /// A dictionary of local variables + /// A dictionary of global variables + /// The resulting Python object + public static PyObject ExecuteExpression(this IPythonEnvironment env, string code, IDictionary locals, IDictionary globals) + { + using (GIL.Acquire()) + { + using var localsPyDict = PyObject.From(locals); + using var globalsPyDict = PyObject.From(globals); + return CPythonAPI.PyRun_String(code, CPythonAPI.InputType.Py_eval_input, globalsPyDict, localsPyDict); + } + } + + /// + /// Execute Python program from a string, typically multiple lines of code + /// + /// The Python code + /// A dictionary of local variables + /// A dictionary of global variables + /// Normally nothing + public static PyObject Execute(this IPythonEnvironment env, string code, IDictionary locals, IDictionary globals) + { + using (GIL.Acquire()) + { + using var localsPyDict = PyObject.From(locals); + using var globalsPyDict = PyObject.From(globals); + return CPythonAPI.PyRun_String(code, CPythonAPI.InputType.Py_file_input, globalsPyDict, localsPyDict); + } + } +}