2 // eval.cs: Evaluation and Hosting API for the C# compiler
5 // Miguel de Icaza (miguel@gnome.org)
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
9 // Copyright 2001, 2002, 2003 Ximian, Inc (http://www.ximian.com)
10 // Copyright 2004, 2005, 2006, 2007, 2008 Novell, Inc
13 using System
.Threading
;
14 using System
.Collections
;
15 using System
.Reflection
;
16 using System
.Reflection
.Emit
;
18 using System
.Globalization
;
21 namespace Mono
.CSharp
{
24 /// Evaluator: provides an API to evaluate C# statements and
25 /// expressions dynamically.
28 /// This class exposes static methods to evaluate expressions in the
31 /// To initialize the evaluator with a number of compiler
32 /// options call the Init(string[]args) method with a set of
33 /// command line options that the compiler recognizes.
35 /// To interrupt execution of a statement, you can invoke the
36 /// Evaluator.Interrupt method.
38 public class Evaluator
{
40 static object evaluator_lock
= new object ();
42 static string current_debug_name
;
44 static Thread invoke_thread
;
46 static ArrayList using_alias_list
= new ArrayList ();
47 static ArrayList using_list
= new ArrayList ();
48 static Hashtable fields
= new Hashtable ();
50 static Type interactive_base_class
= typeof (InteractiveBase
);
55 /// Optional initialization for the Evaluator.
58 /// Initializes the Evaluator with the command line options
59 /// that would be processed by the command line compiler. Only
60 /// the first call to Init will work, any future invocations are
63 /// You can safely avoid calling this method if your application
64 /// does not need any of the features exposed by the command line
67 public static void Init (string [] args
)
69 lock (evaluator_lock
){
73 RootContext
.Version
= LanguageVersion
.Default
;
74 driver
= Driver
.Create (args
, false);
76 throw new Exception ("Failed to create compiler driver with the given arguments");
78 driver
.ProcessDefaultConfig ();
79 CompilerCallableEntryPoint
.Reset ();
80 Driver
.LoadReferences ();
81 RootContext
.EvalMode
= true;
88 Init (new string [0]);
93 CompilerCallableEntryPoint
.PartialReset ();
96 // PartialReset should not reset the core types, this is very redundant.
98 if (!TypeManager
.InitCoreTypes ())
99 throw new Exception ("Failed to InitCoreTypes");
100 TypeManager
.InitOptionalCoreTypes ();
102 Location
.AddFile ("{interactive}");
103 Location
.Initialize ();
105 current_debug_name
= "interactive" + (count
++) + ".dll";
106 if (Environment
.GetEnvironmentVariable ("SAVE") != null){
107 CodeGen
.Init (current_debug_name
, current_debug_name
, false);
109 CodeGen
.InitDynamic (current_debug_name
);
113 /// The base class for the classes that host the user generated code
117 /// This is the base class that will host the code
118 /// executed by the Evaluator. By default
119 /// this is the Mono.CSharp.InteractiveBase class
120 /// which is useful for interactive use.
122 /// By changing this property you can control the
123 /// base class and the static members that are
124 /// available to your evaluated code.
126 static public Type InteractiveBaseClass
{
128 return interactive_base_class
;
133 throw new ArgumentNullException ();
135 lock (evaluator_lock
)
136 interactive_base_class
= value;
141 /// Interrupts the evaluation of an expression executing in Evaluate.
144 /// Use this method to interrupt long-running invocations.
146 public static void Interrupt ()
148 if (!inited
|| !invoking
)
151 if (invoke_thread
!= null)
152 invoke_thread
.Abort ();
156 /// Compiles the input string and returns a delegate that represents the compiled code.
160 /// Compiles the input string as a C# expression or
161 /// statement, unlike the Evaluate method, the
162 /// resulting delegate can be invoked multiple times
163 /// without incurring in the compilation overhead.
165 /// If the return value of this function is null,
166 /// this indicates that the parsing was complete.
167 /// If the return value is a string it indicates
168 /// that the input string was partial and that the
169 /// invoking code should provide more code before
170 /// the code can be successfully compiled.
172 /// If you know that you will always get full expressions or
173 /// statements and do not care about partial input, you can use
174 /// the other Compile overload.
176 /// On success, in addition to returning null, the
177 /// compiled parameter will be set to the delegate
178 /// that can be invoked to execute the code.
181 static public string Compile (string input
, out CompiledMethod compiled
)
183 if (input
== null || input
.Length
== 0){
188 lock (evaluator_lock
){
193 CSharpParser parser
= ParseString (true, input
, out partial_input
);
199 ParseString (false, input
, out partial_input
);
203 object parser_result
= parser
.InteractiveResult
;
205 if (!(parser_result
is Class
)){
206 int errors
= Report
.Errors
;
208 NamespaceEntry
.VerifyAllUsing ();
209 if (errors
== Report
.Errors
)
210 parser
.CurrentNamespace
.Extract (using_alias_list
, using_list
);
213 compiled
= CompileBlock (parser_result
as Class
, parser
.undo
);
220 /// Compiles the input string and returns a delegate that represents the compiled code.
224 /// Compiles the input string as a C# expression or
225 /// statement, unlike the Evaluate method, the
226 /// resulting delegate can be invoked multiple times
227 /// without incurring in the compilation overhead.
229 /// This method can only deal with fully formed input
230 /// strings and does not provide a completion mechanism.
231 /// If you must deal with partial input (for example for
232 /// interactive use) use the other overload.
234 /// On success, a delegate is returned that can be used
235 /// to invoke the method.
238 static public CompiledMethod
Compile (string input
)
240 CompiledMethod compiled
;
242 // Ignore partial inputs
243 if (Compile (input
, out compiled
) != null){
244 // Error, the input was partial.
248 // Either null (on error) or the compiled method.
253 // Todo: Should we handle errors, or expect the calling code to setup
254 // the recording themselves?
258 /// Evaluates and expression or statement and returns any result values.
261 /// Evaluates the input string as a C# expression or
262 /// statement. If the input string is an expression
263 /// the result will be stored in the result variable
264 /// and the result_set variable will be set to true.
266 /// It is necessary to use the result/result_set
267 /// pair to identify when a result was set (for
268 /// example, execution of user-provided input can be
269 /// an expression, a statement or others, and
270 /// result_set would only be set if the input was an
273 /// If the return value of this function is null,
274 /// this indicates that the parsing was complete.
275 /// If the return value is a string, it indicates
276 /// that the input is partial and that the user
277 /// should provide an updated string.
279 public static string Evaluate (string input
, out object result
, out bool result_set
)
281 CompiledMethod compiled
;
286 input
= Compile (input
, out compiled
);
290 if (compiled
== null)
294 // The code execution does not need to keep the compiler lock
296 object retval
= typeof (NoValueSet
);
299 invoke_thread
= System
.Threading
.Thread
.CurrentThread
;
301 compiled (ref retval
);
302 } catch (ThreadAbortException e
){
303 Thread
.ResetAbort ();
304 Console
.WriteLine ("Interrupted!\n{0}", e
);
310 // We use a reference to a compiler type, in this case
311 // Driver as a flag to indicate that this was a statement
313 if (retval
!= typeof (NoValueSet
)){
322 /// Executes the given expression or statement.
325 /// Executes the provided statement, returns true
326 /// on success, false on parsing errors. Exceptions
327 /// might be thrown by the called code.
329 public static bool Run (string statement
)
337 bool ok
= Evaluate (statement
, out result
, out result_set
) == null;
343 /// Evaluates and expression or statement and returns the result.
346 /// Evaluates the input string as a C# expression or
347 /// statement and returns the value.
349 /// This method will throw an exception if there is a syntax error,
350 /// of if the provided input is not an expression but a statement.
352 public static object Evaluate (string input
)
357 string r
= Evaluate (input
, out result
, out result_set
);
360 throw new ArgumentException ("Syntax error on input: partial input");
362 if (result_set
== false)
363 throw new ArgumentException ("The expression did not set a result");
370 StatementOrExpression
,
376 // Deambiguates the input string to determine if we
377 // want to process a statement or if we want to
378 // process a compilation unit.
380 // This is done using a top-down predictive parser,
381 // since the yacc/jay parser can not deambiguage this
382 // without more than one lookahead token. There are very
385 static InputKind
ToplevelOrStatement (SeekableStreamReader seekable
)
387 Tokenizer tokenizer
= new Tokenizer (seekable
, Location
.SourceFiles
[0]);
389 int t
= tokenizer
.token ();
392 return InputKind
.EOF
;
394 // These are toplevels
396 case Token
.OPEN_BRACKET
:
400 case Token
.INTERFACE
:
402 case Token
.NAMESPACE
:
404 case Token
.PROTECTED
:
409 return InputKind
.CompilationUnit
;
411 // Definitely expression
428 return InputKind
.StatementOrExpression
;
430 // These need deambiguation help
432 t
= tokenizer
.token ();
434 return InputKind
.EOF
;
436 if (t
== Token
.IDENTIFIER
)
437 return InputKind
.CompilationUnit
;
438 return InputKind
.StatementOrExpression
;
441 // Distinguish between:
442 // delegate opt_anonymous_method_signature block
445 t
= tokenizer
.token ();
447 return InputKind
.EOF
;
448 if (t
== Token
.OPEN_PARENS
|| t
== Token
.OPEN_BRACE
)
449 return InputKind
.StatementOrExpression
;
450 return InputKind
.CompilationUnit
;
452 // Distinguih between:
454 // unsafe as modifier of a type declaration
456 t
= tokenizer
.token ();
458 return InputKind
.EOF
;
459 if (t
== Token
.OPEN_PARENS
)
460 return InputKind
.StatementOrExpression
;
461 return InputKind
.CompilationUnit
;
463 // These are errors: we list explicitly what we had
464 // from the grammar, ERROR and then everything else
469 return InputKind
.Error
;
471 // This catches everything else allowed by
472 // expressions. We could add one-by-one use cases
475 return InputKind
.StatementOrExpression
;
480 // Parses the string @input and returns a CSharpParser if succeeful.
482 // if @silent is set to true then no errors are
483 // reported to the user. This is used to do various calls to the
484 // parser and check if the expression is parsable.
486 // @partial_input: if @silent is true, then it returns whether the
487 // parsed expression was partial, and more data is needed
489 static CSharpParser
ParseString (bool silent
, string input
, out bool partial_input
)
491 partial_input
= false;
493 queued_fields
.Clear ();
495 Stream s
= new MemoryStream (Encoding
.Default
.GetBytes (input
));
496 SeekableStreamReader seekable
= new SeekableStreamReader (s
, Encoding
.Default
);
498 InputKind kind
= ToplevelOrStatement (seekable
);
499 if (kind
== InputKind
.Error
){
501 Report
.Error (-25, "Detection Parsing Error");
502 partial_input
= false;
506 if (kind
== InputKind
.EOF
){
508 Console
.Error
.WriteLine ("Internal error: EOF condition should have been detected in a previous call with silent=true");
509 partial_input
= true;
513 seekable
.Position
= 0;
515 CSharpParser parser
= new CSharpParser (seekable
, Location
.SourceFiles
[0]);
516 parser
.ErrorOutput
= Report
.Stderr
;
518 if (kind
== InputKind
.StatementOrExpression
){
519 parser
.Lexer
.putback_char
= Tokenizer
.EvalStatementParserCharacter
;
520 RootContext
.StatementMode
= true;
523 // Do not activate EvalCompilationUnitParserCharacter until
524 // I have figured out all the limitations to invoke methods
525 // in the generated classes. See repl.txt
527 parser
.Lexer
.putback_char
= Tokenizer
.EvalUsingDeclarationsParserCharacter
;
528 //parser.Lexer.putback_char = Tokenizer.EvalCompilationUnitParserCharacter;
529 RootContext
.StatementMode
= false;
533 Report
.DisableReporting ();
537 if (Report
.Errors
!= 0){
538 if (silent
&& parser
.UnexpectedEOF
)
539 partial_input
= true;
541 parser
.undo
.ExecuteUndo ();
546 Report
.EnableReporting ();
552 // Queue all the fields that we use, as we need to then go from FieldBuilder to FieldInfo
553 // or reflection gets confused (it basically gets confused, and variables override each
556 static ArrayList queued_fields
= new ArrayList ();
558 //static ArrayList types = new ArrayList ();
560 static volatile bool invoking
;
562 static CompiledMethod
CompileBlock (Class host
, Undo undo
)
564 RootContext
.ResolveTree ();
565 if (Report
.Errors
!= 0){
570 RootContext
.PopulateTypes ();
572 if (Report
.Errors
!= 0){
577 TypeBuilder tb
= null;
578 MethodBuilder mb
= null;
581 tb
= host
.TypeBuilder
;
583 foreach (MemberCore member
in host
.Methods
){
584 if (member
.Name
!= "Host")
587 MethodOrOperator method
= (MethodOrOperator
) member
;
588 mb
= method
.MethodBuilder
;
593 throw new Exception ("Internal error: did not find the method builder for the generated method");
596 RootContext
.EmitCode ();
597 if (Report
.Errors
!= 0)
600 RootContext
.CloseTypes ();
602 if (Environment
.GetEnvironmentVariable ("SAVE") != null)
603 CodeGen
.Save (current_debug_name
, false);
609 // Unlike Mono, .NET requires that the MethodInfo is fetched, it cant
610 // work from MethodBuilders. Retarded, I know.
612 Type tt
= CodeGen
.Assembly
.Builder
.GetType (tb
.Name
);
613 MethodInfo mi
= tt
.GetMethod (mb
.Name
);
615 // Pull the FieldInfos from the type, and keep track of them
616 foreach (Field field
in queued_fields
){
617 FieldInfo fi
= tt
.GetField (field
.Name
);
619 FieldInfo old
= (FieldInfo
) fields
[field
.Name
];
621 // If a previous value was set, nullify it, so that we do
624 if (old
.FieldType
.IsValueType
){
626 // TODO: Clear fields for structs
630 old
.SetValue (null, null);
636 fields
[field
.Name
] = fi
;
640 queued_fields
.Clear ();
642 return (CompiledMethod
) System
.Delegate
.CreateDelegate (typeof (CompiledMethod
), mi
);
645 static internal void LoadAliases (NamespaceEntry ns
)
647 ns
.Populate (using_alias_list
, using_list
);
651 /// A sentinel value used to indicate that no value was
652 /// was set by the compiled function. This is used to
653 /// differentiate between a function not returning a
656 public class NoValueSet
{
659 static internal FieldInfo
LookupField (string name
)
661 FieldInfo fi
= (FieldInfo
) fields
[name
];
667 // Puts the FieldBuilder into a queue of names that will be
668 // registered. We can not register FieldBuilders directly
669 // we need to fetch the FieldInfo after Reflection cooks the
670 // types, or bad things happen (bad means: FieldBuilders behave
671 // incorrectly across multiple assemblies, causing assignments to
674 // This also serves for the parser to register Field classes
675 // that should be exposed as global variables
677 static internal void QueueField (Field f
)
679 queued_fields
.Add (f
);
682 static string Quote (string s
)
684 if (s
.IndexOf ('"') != -1)
685 s
= s
.Replace ("\"", "\\\"");
687 return "\"" + s
+ "\"";
690 static public string GetUsing ()
692 lock (evaluator_lock
){
693 StringBuilder sb
= new StringBuilder ();
695 foreach (object x
in using_alias_list
)
696 sb
.Append (String
.Format ("using {0};\n", x
));
698 foreach (object x
in using_list
)
699 sb
.Append (String
.Format ("using {0};\n", x
));
701 return sb
.ToString ();
705 static public string GetVars ()
707 lock (evaluator_lock
){
708 StringBuilder sb
= new StringBuilder ();
710 foreach (DictionaryEntry de
in fields
){
711 FieldInfo fi
= LookupField ((string) de
.Key
);
718 value = fi
.GetValue (null);
720 value = Quote ((string)value);
726 sb
.Append (String
.Format ("{0} {1} <error reading value>", TypeManager
.CSharpName(fi
.FieldType
), de
.Key
));
728 sb
.Append (String
.Format ("{0} {1} = {2}", TypeManager
.CSharpName(fi
.FieldType
), de
.Key
, value));
731 return sb
.ToString ();
736 /// Loads the given assembly and exposes the API to the user.
738 static public void LoadAssembly (string file
)
740 lock (evaluator_lock
){
741 Driver
.LoadAssembly (file
, false);
742 RootNamespace
.ComputeNamespaces ();
747 /// Exposes the API of the given assembly to the Evaluator
749 static public void ReferenceAssembly (Assembly a
)
751 lock (evaluator_lock
){
752 RootNamespace
.Global
.AddAssemblyReference (a
);
753 RootNamespace
.ComputeNamespaces ();
761 /// A delegate that can be used to invoke the
762 /// compiled expression or statement.
765 /// Since the Compile methods will compile
766 /// statements and expressions into the same
767 /// delegate, you can tell if a value was returned
768 /// by checking whether the returned value is of type
772 public delegate void CompiledMethod (ref object retvalue
);
775 /// The default base class for every interaction line
778 /// The expressions and statements behave as if they were
779 /// a static method of this class. The InteractiveBase class
780 /// contains a number of useful methods, but can be overwritten
781 /// by setting the InteractiveBaseType property in the Evaluator
783 public class InteractiveBase
{
785 /// Determines where the standard output of methods in this class will go.
787 public static TextWriter Output
= Console
.Out
;
790 /// Determines where the standard error of methods in this class will go.
792 public static TextWriter Error
= Console
.Error
;
795 /// The primary prompt used for interactive use.
797 public static string Prompt
= "csharp> ";
800 /// The secondary prompt used for interactive use (used when
801 /// an expression is incomplete).
803 public static string ContinuationPrompt
= " > ";
806 /// Used to signal that the user has invoked the `quit' statement.
808 public static bool QuitRequested
;
811 /// Shows all the variables defined so far.
813 static public void ShowVars ()
815 Output
.Write (Evaluator
.GetVars ());
820 /// Displays the using statements in effect at this point.
822 static public void ShowUsing ()
824 Output
.Write (Evaluator
.GetUsing ());
828 public delegate void Simple ();
831 /// Times the execution of the given delegate
833 static public TimeSpan
Time (Simple a
)
835 DateTime start
= DateTime
.Now
;
837 return DateTime
.Now
- start
;
842 /// Loads the assemblies from a package
845 /// Loads the assemblies from a package. This is equivalent
846 /// to passing the -pkg: command line flag to the C# compiler
847 /// on the command line.
849 static public void LoadPackage (string pkg
)
852 Error
.WriteLine ("Invalid package specified");
856 string pkgout
= Driver
.GetPackageFlags (pkg
, false);
860 string [] xargs
= pkgout
.Trim (new Char
[] {' ', '\n', '\r', '\t'}
).
861 Split (new Char
[] { ' ', '\t'}
);
863 foreach (string s
in xargs
){
864 if (s
.StartsWith ("-r:") || s
.StartsWith ("/r:") || s
.StartsWith ("/reference:")){
865 string lib
= s
.Substring (s
.IndexOf (':')+1);
867 Evaluator
.LoadAssembly (lib
);
875 /// Loads the assembly
878 /// Loads the specified assembly and makes its types
879 /// available to the evaluator. This is equivalent
880 /// to passing the -pkg: command line flag to the C#
881 /// compiler on the command line.
883 static public void LoadAssembly (string assembly
)
885 Evaluator
.LoadAssembly (assembly
);
889 /// Returns a list of available static methods.
891 static public string help
{
893 return "Static methods:\n"+
894 " Describe(obj) - Describes the object's type\n" +
895 " LoadPackage (pkg); - Loads the given Package (like -pkg:FILE)\n" +
896 " LoadAssembly (ass) - Loads the given assembly (like -r:ASS)\n" +
897 " ShowVars (); - Shows defined local variables.\n" +
898 " ShowUsing (); - Show active using decltions.\n" +
899 " Prompt - The prompt used by the C# shell\n" +
900 " ContinuationPrompt - The prompt for partial input\n" +
901 " Time(() -> { }) - Times the specified code\n" +
908 /// Indicates to the read-eval-print-loop that the interaction should be finished.
910 static public object quit
{
912 QuitRequested
= true;
919 /// Describes an object or a type.
922 /// This method will show a textual representation
923 /// of the object's type. If the object is a
924 /// System.Type it renders the type directly,
925 /// otherwise it renders the type returned by
926 /// invoking GetType on the object.
928 static public string Describe (object x
)
937 StringWriter sw
= new StringWriter ();
938 new Outline (t
, sw
, true, false, false).OutlineType ();
939 return sw
.ToString ();
945 // A local variable reference that will create a Field in a
946 // Class with the resolved type. This is necessary so we can
947 // support "var" as a field type in a class declaration.
949 // We allow LocalVariableReferece to do the heavy lifting, and
950 // then we insert the field with the resolved type
952 public class LocalVariableReferenceWithClassSideEffect
: LocalVariableReference
{
953 TypeContainer container
;
956 public LocalVariableReferenceWithClassSideEffect (TypeContainer container
, string name
, Block current_block
, string local_variable_id
, Location loc
)
957 : base (current_block
, local_variable_id
, loc
)
959 this.container
= container
;
963 public override bool Equals (object obj
)
965 LocalVariableReferenceWithClassSideEffect lvr
= obj
as LocalVariableReferenceWithClassSideEffect
;
969 if (lvr
.name
!= name
|| lvr
.container
!= container
)
972 return base.Equals (obj
);
975 public override int GetHashCode ()
977 return name
.GetHashCode ();
980 override public Expression
DoResolveLValue (EmitContext ec
, Expression right_side
)
982 Expression ret
= base.DoResolveLValue (ec
, right_side
);
986 Field f
= new Field (container
, new TypeExpression (ret
.Type
, Location
),
987 Modifiers
.PUBLIC
| Modifiers
.STATIC
,
988 new MemberName (name
, Location
), null);
989 container
.AddField (f
);
991 Evaluator
.QueueField (f
);
998 /// A class used to assign values if the source expression is not void
1000 /// Used by the interactive shell to allow it to call this code to set
1001 /// the return value for an invocation.
1003 class OptionalAssign
: SimpleAssign
{
1004 public OptionalAssign (Expression t
, Expression s
, Location loc
)
1009 public override Expression
DoResolve (EmitContext ec
)
1011 CloneContext cc
= new CloneContext ();
1012 Expression clone
= source
.Clone (cc
);
1014 clone
= clone
.Resolve (ec
);
1018 // This means its really a statement.
1019 if (clone
.Type
== TypeManager
.void_type
){
1020 source
= source
.Resolve (ec
);
1022 type
= TypeManager
.void_type
;
1023 eclass
= ExprClass
.Value
;
1027 return base.DoResolve (ec
);
1030 public override void Emit (EmitContext ec
)
1038 public override void EmitStatement (EmitContext ec
)
1043 base.EmitStatement (ec
);
1048 ArrayList undo_types
;
1052 undo_types
= new ArrayList ();
1055 public void AddTypeContainer (TypeContainer current_container
, TypeContainer tc
)
1057 if (current_container
== tc
){
1058 Console
.Error
.WriteLine ("Internal error: inserting container into itself");
1062 if (undo_types
== null)
1063 undo_types
= new ArrayList ();
1064 undo_types
.Add (new Pair (current_container
, tc
));
1067 public void ExecuteUndo ()
1069 if (undo_types
== null)
1072 foreach (Pair p
in undo_types
){
1073 TypeContainer current_container
= (TypeContainer
) p
.First
;
1075 current_container
.RemoveTypeContainer ((TypeContainer
) p
.Second
);