11using System ;
22using System . Collections . Concurrent ;
3- using System . ComponentModel ;
43using System . Runtime . CompilerServices ;
54using System . Text ;
65using Python . Runtime ;
@@ -10,6 +9,35 @@ namespace PythonNETExtensions.Core
109{
1110 public static class RawPython
1211 {
12+ public enum CompilationOption
13+ {
14+ Auto ,
15+ Compile ,
16+ ExecuteOnly
17+ }
18+
19+ public interface IRunOptions
20+ {
21+ public static abstract CompilationOption CompilationOption { get ; }
22+ public static abstract bool UseCachedScope { get ; }
23+ }
24+
25+ public struct DefaultRunOptions : IRunOptions
26+ {
27+ public static CompilationOption CompilationOption => CompilationOption . Auto ;
28+ public static bool UseCachedScope => false ;
29+ }
30+
31+ public struct CachedScopeRunOptions : IRunOptions
32+ {
33+ public static CompilationOption CompilationOption => CompilationOption . Auto ;
34+
35+ // Use new module, this is so that interpolated variables are "captured".
36+ public static bool UseCachedScope => true ;
37+ }
38+
39+ private struct NoRet { }
40+
1341 private struct StringBuilderThreadStaticDefinition : IThreadStaticDefinition < StringBuilder >
1442 {
1543 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
@@ -38,7 +66,7 @@ public static void OnGet(ref PyModule item) { }
3866 }
3967
4068 [ InterpolatedStringHandler ]
41- public struct CodeInterpolator
69+ public struct CodeInterpolator < OptsT > where OptsT : IRunOptions
4270 {
4371 private readonly StringBuilder LocalStringBuilder ;
4472
@@ -47,16 +75,16 @@ public struct CodeInterpolator
4775 private int CurrentObjectIndex ;
4876
4977 public bool ShouldCompile ;
50-
78+
5179 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
5280 public CodeInterpolator ( int literalLength , int formattedCount )
5381 {
5482 // StringBuilderThreadStaticDefinition clears the StringBuilder for us
5583 var stringBuilder = LocalStringBuilder = ThreadStatic < StringBuilderThreadStaticDefinition , StringBuilder > . Item ;
5684 stringBuilder . EnsureCapacity ( literalLength ) ;
5785
58- Scope = ThreadStatic < PyScopeThreadStaticDefinition , PyModule > . Item ;
59- ShouldCompile = true ;
86+ Scope = OptsT . UseCachedScope ? ThreadStatic < PyScopeThreadStaticDefinition , PyModule > . Item : Py . CreateScope ( "NoCache" ) ;
87+ ShouldCompile = OptsT . CompilationOption != CompilationOption . ExecuteOnly ;
6088 }
6189
6290 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
@@ -69,7 +97,7 @@ public void AppendLiteral(string text)
6997 public void AppendFormatted ( string text )
7098 {
7199 // If any of the formatted strings are not interned, we shouldn't compile
72- if ( string . IsInterned ( text ) == null )
100+ if ( OptsT . CompilationOption == CompilationOption . Auto && string . IsInterned ( text ) == null )
73101 {
74102 ShouldCompile = false ;
75103 }
@@ -112,64 +140,67 @@ public override string ToString()
112140 return LocalStringBuilder . ToString ( ) ;
113141 }
114142 }
143+
144+ private static readonly ConcurrentDictionary < string , PyObject > CODE_TO_COMPILATION_MAP = new ( ) ;
115145
116- public enum CompilationOption
146+ private const string RET_VAR_NAME = "py_ret" ;
147+
148+ public static void Run ( CodeInterpolator < DefaultRunOptions > code )
117149 {
118- Auto ,
119- Compile ,
120- ExecuteOnly
150+ Run < NoRet , DefaultRunOptions > ( code ) ;
121151 }
122-
123- private static readonly ConcurrentDictionary < string , PyObject > CODE_TO_COMPILATION_MAP = new ( ) ;
124152
125- public static void Run ( CodeInterpolator code , CompilationOption compilationOption = CompilationOption . Auto )
153+ public static void RunWithCachedScope ( CodeInterpolator < CachedScopeRunOptions > code )
126154 {
127- var codeText = code . ToString ( ) ;
128- RunInternal ( codeText , code , compilationOption ) ;
155+ Run < NoRet , CachedScopeRunOptions > ( code ) ;
129156 }
130157
131- public static RetT Run < RetT > ( CodeInterpolator code , CompilationOption compilationOption = CompilationOption . Auto )
158+ public static RetT Run < RetT > ( CodeInterpolator < DefaultRunOptions > code )
132159 {
133- var codeText = code . ToString ( ) ;
160+ return Run < RetT , DefaultRunOptions > ( code ) ;
161+ }
162+
163+ public static RetT RunWithCachedScope < RetT > ( CodeInterpolator < CachedScopeRunOptions > code )
164+ {
165+ return Run < RetT , CachedScopeRunOptions > ( code ) ;
166+ }
167+
168+ public static RetT Run < RetT , OptsT > ( CodeInterpolator < OptsT > code )
169+ where OptsT : IRunOptions
170+ {
171+ var hasRet = typeof ( RetT ) != typeof ( NoRet ) ;
134172
135- const string METHOD_WRAPPER_NAME = "py_wrapper_method" , RET_VAR_NAME = "py_ret" ;
173+ var codeText = code . ToString ( ) ;
174+
175+ const string METHOD_WRAPPER_NAME = "py_wrapper_method" ;
136176
137177 // TODO: Somehow optimize performance of this
138- codeText =
178+ codeText = hasRet ?
139179 $ """
140180 def { METHOD_WRAPPER_NAME } ():
141181 { codeText . IndentCode ( ) }
142182
143183 { RET_VAR_NAME } = { METHOD_WRAPPER_NAME } ();
144- """ ;
145-
146- RunInternal ( codeText , code , compilationOption ) ;
184+ """ : codeText ;
147185
148- return code . Scope . Get < RetT > ( RET_VAR_NAME ) ;
149- }
150-
151- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
152- private static void RunInternal ( string codeText , CodeInterpolator code , CompilationOption compilationOption )
153- {
154186 var scope = code . Scope ;
155187
156- bool shouldCompile ;
157-
158- switch ( compilationOption )
188+ var shouldCompile = code . ShouldCompile ;
189+
190+ if ( false )
159191 {
160- case CompilationOption . Auto :
161- shouldCompile = code . ShouldCompile ;
162- break ;
163- case CompilationOption . Compile :
164- shouldCompile = true ;
165- break ;
166- case CompilationOption . ExecuteOnly :
167- shouldCompile = false ;
168- break ;
169- default :
170- throw new InvalidEnumArgumentException ( ) ;
192+ Console . WriteLine (
193+ $ """
194+ Compiled: { shouldCompile }
195+
196+ Codegen:
197+ { codeText }
198+
199+ Scope: { scope }
200+ Variables: { scope . Variables ( ) . Keys ( ) }
201+ """ ) ;
171202 }
172-
203+
173204 if ( shouldCompile )
174205 {
175206 var compilations = CODE_TO_COMPILATION_MAP ;
@@ -186,17 +217,15 @@ private static void RunInternal(string codeText, CodeInterpolator code, Compilat
186217 {
187218 scope . Exec ( codeText ) ;
188219 }
220+
221+ var ret = scope . Get < RetT > ( RET_VAR_NAME ) ;
189222
190- if ( false )
223+ if ( OptsT . UseCachedScope )
191224 {
192- Console . WriteLine (
193- $ """
194- Compiled: { shouldCompile }
195-
196- Codegen:
197- { codeText }
198- """ ) ;
225+ scope . Variables ( ) . Clear ( ) ;
199226 }
227+
228+ return ret ;
200229 }
201230 }
202231}
0 commit comments