Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.NRefactory/5.5.0/NRefactory.CSharp-5.5.0/Parser/mcs/eval.cs @ 18242

Last change on this file since 18242 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 35.4 KB
Line 
1//
2// eval.cs: Evaluation and Hosting API for the C# compiler
3//
4// Authors:
5//   Miguel de Icaza (miguel@gnome.org)
6//   Marek Safar (marek.safar@gmail.com)
7//
8// Dual licensed under the terms of the MIT X11 or GNU GPL
9//
10// Copyright 2001, 2002, 2003 Ximian, Inc (http://www.ximian.com)
11// Copyright 2004-2011 Novell, Inc
12// Copyright 2011 Xamarin Inc
13//
14
15using System;
16using System.Threading;
17using System.Collections.Generic;
18using System.Reflection;
19using System.Reflection.Emit;
20using System.IO;
21using System.Text;
22using System.Linq;
23
24namespace Mono.CSharp
25{
26
27  /// <summary>
28  /// Experimental!
29  /// </summary>
30  public delegate void ValueModificationHandler (string variableName, int row, int column, object value);
31
32  /// <summary>
33  ///   Evaluator: provides an API to evaluate C# statements and
34  ///   expressions dynamically.
35  /// </summary>
36  /// <remarks>
37  ///   This class exposes static methods to evaluate expressions in the
38  ///   current program.
39  ///
40  ///   To initialize the evaluator with a number of compiler
41  ///   options call the Init(string[]args) method with a set of
42  ///   command line options that the compiler recognizes.
43  ///
44  ///   To interrupt execution of a statement, you can invoke the
45  ///   Evaluator.Interrupt method.
46  /// </remarks>
47  public class Evaluator {
48
49    enum ParseMode {
50      // Parse silently, do not output any error messages
51      Silent,
52
53      // Report errors during parse
54      ReportErrors,
55
56      // Auto-complete, means that the tokenizer will start producing
57      // GETCOMPLETIONS tokens when it reaches a certain point.
58      GetCompletions
59    }
60
61    static object evaluator_lock = new object ();
62    static volatile bool invoking;
63   
64#if !STATIC
65    static int count;
66#endif
67    static Thread invoke_thread;
68
69    readonly Dictionary<string, Tuple<FieldSpec, FieldInfo>> fields;
70
71    Type base_class;
72    bool inited;
73    int startup_files;
74
75    readonly CompilerContext ctx;
76    readonly ModuleContainer module;
77    readonly ReflectionImporter importer;
78    readonly CompilationSourceFile source_file;
79
80    int? listener_id;
81   
82    public Evaluator (CompilerContext ctx)
83    {
84      this.ctx = ctx;
85
86      module = new ModuleContainer (ctx);
87      module.Evaluator = this;
88
89      source_file = new CompilationSourceFile (module, null);
90      module.AddTypeContainer (source_file);
91
92      startup_files = ctx.SourceFiles.Count;
93
94      // FIXME: Importer needs this assembly for internalsvisibleto
95      module.SetDeclaringAssembly (new AssemblyDefinitionDynamic (module, "evaluator"));
96      importer = new ReflectionImporter (module, ctx.BuiltinTypes);
97
98      InteractiveBaseClass = typeof (InteractiveBase);
99      fields = new Dictionary<string, Tuple<FieldSpec, FieldInfo>> ();
100    }
101
102    void Init ()
103    {
104      var loader = new DynamicLoader (importer, ctx);
105
106      RootContext.ToplevelTypes = module;
107
108      //var startup_files = new List<string> ();
109      //foreach (CompilationUnit file in Location.SourceFiles)
110      //    startup_files.Add (file.Path);
111
112      loader.LoadReferences (module);
113      ctx.BuiltinTypes.CheckDefinitions (module);
114      module.InitializePredefinedTypes ();
115
116      inited = true;
117    }
118
119    void ParseStartupFiles ()
120    {
121      Driver d = new Driver (ctx);
122
123      Location.Initialize (ctx.SourceFiles);
124
125      var parser_session = new ParserSession ();
126      for (int i = 0; i < startup_files; ++i) {
127        var sf = ctx.SourceFiles [i];
128        d.Parse (sf, module, parser_session, ctx.Report);
129      }
130    }
131
132    void Reset ()
133    {
134      Location.Reset ();
135      Location.Initialize (ctx.SourceFiles);
136    }
137
138    /// <summary>
139    /// When set evaluator will automatically wait on Task of async methods. When not
140    /// set it's called responsibility to handle Task execution
141    /// </summary>
142    public bool WaitOnTask { get; set; }
143
144    /// <summary>
145    ///   If true, turns type expressions into valid expressions
146    ///   and calls the describe method on it
147    /// </summary>
148    public bool DescribeTypeExpressions;
149
150    /// <summary>
151    ///   Whether the evaluator will use terse syntax, and the semicolons at the end are optional
152    /// </summary>
153    public bool Terse = true;
154
155    /// <summary>
156    ///   The base class for the classes that host the user generated code
157    /// </summary>
158    /// <remarks>
159    ///
160    ///   This is the base class that will host the code
161    ///   executed by the Evaluator.  By default
162    ///   this is the Mono.CSharp.InteractiveBase class
163    ///   which is useful for interactive use.
164    ///
165    ///   By changing this property you can control the
166    ///   base class and the static members that are
167    ///   available to your evaluated code.
168    /// </remarks>
169    public Type InteractiveBaseClass {
170      get {
171        return base_class;
172      }
173      set {
174        base_class = value;
175
176        if (value != null && typeof (InteractiveBase).IsAssignableFrom (value))
177          InteractiveBase.Evaluator = this;
178      }
179    }
180
181    /// <summary>
182    ///   Interrupts the evaluation of an expression executing in Evaluate.
183    /// </summary>
184    /// <remarks>
185    ///   Use this method to interrupt long-running invocations.
186    /// </remarks>
187    public void Interrupt ()
188    {
189      if (!inited || !invoking)
190        return;
191     
192      if (invoke_thread != null)
193        invoke_thread.Abort ();
194    }
195
196    /// <summary>
197    ///   Compiles the input string and returns a delegate that represents the compiled code.
198    /// </summary>
199    /// <remarks>
200    ///
201    ///   Compiles the input string as a C# expression or
202    ///   statement, unlike the Evaluate method, the
203    ///   resulting delegate can be invoked multiple times
204    ///   without incurring in the compilation overhead.
205    ///
206    ///   If the return value of this function is null,
207    ///   this indicates that the parsing was complete.
208    ///   If the return value is a string it indicates
209    ///   that the input string was partial and that the
210    ///   invoking code should provide more code before
211    ///   the code can be successfully compiled.
212    ///
213    ///   If you know that you will always get full expressions or
214    ///   statements and do not care about partial input, you can use
215    ///   the other Compile overload.
216    ///
217    ///   On success, in addition to returning null, the
218    ///   compiled parameter will be set to the delegate
219    ///   that can be invoked to execute the code.
220    ///
221    /// </remarks>
222    public string Compile (string input, out CompiledMethod compiled)
223    {
224      if (input == null || input.Length == 0){
225        compiled = null;
226        return null;
227      }
228
229      lock (evaluator_lock){
230        if (!inited) {
231          Init ();
232          ParseStartupFiles ();
233        } else {
234          ctx.Report.Printer.Reset ();
235        }
236
237        bool partial_input;
238        CSharpParser parser = ParseString (ParseMode.Silent, input, out partial_input);
239
240        // Terse mode, try to provide the trailing semicolon automatically.
241        if (parser == null && Terse && partial_input){
242          bool ignore;
243
244          // check if the source would compile with a block, if so, we should not
245          // add the semicolon.
246          var needs_block = ParseString (ParseMode.Silent, input + "{}", out ignore) != null;
247          if (!needs_block)
248            parser = ParseString (ParseMode.Silent, input + ";", out ignore);
249        }
250        if (parser == null){
251          compiled = null;
252          if (partial_input)
253            return input;
254         
255          ParseString (ParseMode.ReportErrors, input, out partial_input);
256          return null;
257        }
258       
259        Class parser_result = parser.InteractiveResult;
260        compiled = CompileBlock (parser_result, parser.undo, ctx.Report);
261        return null;
262      }
263    }
264
265    /// <summary>
266    ///   Compiles the input string and returns a delegate that represents the compiled code.
267    /// </summary>
268    /// <remarks>
269    ///
270    ///   Compiles the input string as a C# expression or
271    ///   statement, unlike the Evaluate method, the
272    ///   resulting delegate can be invoked multiple times
273    ///   without incurring in the compilation overhead.
274    ///
275    ///   This method can only deal with fully formed input
276    ///   strings and does not provide a completion mechanism.
277    ///   If you must deal with partial input (for example for
278    ///   interactive use) use the other overload.
279    ///
280    ///   On success, a delegate is returned that can be used
281    ///   to invoke the method.
282    ///
283    /// </remarks>
284    public CompiledMethod Compile (string input)
285    {
286      CompiledMethod compiled;
287
288      // Ignore partial inputs
289      if (Compile (input, out compiled) != null){
290        // Error, the input was partial.
291        return null;
292      }
293
294      // Either null (on error) or the compiled method.
295      return compiled;
296    }
297
298    static MethodInfo listener_proxy_value;
299    internal void EmitValueChangedCallback (EmitContext ec, string name, TypeSpec type, Location loc)
300    {
301      if (listener_id == null)
302        listener_id = ListenerProxy.Register (ModificationListener);
303
304      if (listener_proxy_value == null)
305        listener_proxy_value = typeof (ListenerProxy).GetMethod ("ValueChanged");
306
307#if STATIC
308      throw new NotSupportedException ();
309#else
310      // object value, int row, int col, string name, int listenerId
311      if (type.IsStructOrEnum)
312        ec.Emit (OpCodes.Box, type);
313
314      ec.EmitInt (loc.Row);
315      ec.EmitInt (loc.Column);
316      ec.Emit (OpCodes.Ldstr, name);
317      ec.EmitInt (listener_id.Value);
318      ec.Emit (OpCodes.Call, listener_proxy_value);
319#endif
320    }
321
322    /// <summary>
323    ///   Evaluates and expression or statement and returns any result values.
324    /// </summary>
325    /// <remarks>
326    ///   Evaluates the input string as a C# expression or
327    ///   statement.  If the input string is an expression
328    ///   the result will be stored in the result variable
329    ///   and the result_set variable will be set to true.
330    ///
331    ///   It is necessary to use the result/result_set
332    ///   pair to identify when a result was set (for
333    ///   example, execution of user-provided input can be
334    ///   an expression, a statement or others, and
335    ///   result_set would only be set if the input was an
336    ///   expression.
337    ///
338    ///   If the return value of this function is null,
339    ///   this indicates that the parsing was complete.
340    ///   If the return value is a string, it indicates
341    ///   that the input is partial and that the user
342    ///   should provide an updated string.
343    /// </remarks>
344    public string Evaluate (string input, out object result, out bool result_set)
345    {
346      CompiledMethod compiled;
347
348      result_set = false;
349      result = null;
350
351      input = Compile (input, out compiled);
352      if (input != null)
353        return input;
354     
355      if (compiled == null)
356        return null;
357       
358      //
359      // The code execution does not need to keep the compiler lock
360      //
361      object retval = typeof (QuitValue);
362
363      try {
364        invoke_thread = System.Threading.Thread.CurrentThread;
365        invoking = true;
366        compiled (ref retval);
367      } catch (ThreadAbortException e){
368        Thread.ResetAbort ();
369        Console.WriteLine ("Interrupted!\n{0}", e);
370      } finally {
371        invoking = false;
372
373        if (listener_id != null) {
374          ListenerProxy.Unregister (listener_id.Value);
375          listener_id = null;
376        }
377      }
378
379      //
380      // We use a reference to a compiler type, in this case
381      // Driver as a flag to indicate that this was a statement
382      //
383      if (!ReferenceEquals (retval, typeof (QuitValue))) {
384        result_set = true;
385        result = retval;
386      }
387
388      return null;
389    }
390
391    public string [] GetCompletions (string input, out string prefix)
392    {
393      prefix = "";
394      if (input == null || input.Length == 0)
395        return null;
396     
397      lock (evaluator_lock){
398        if (!inited)
399          Init ();
400       
401        bool partial_input;
402        CSharpParser parser = ParseString (ParseMode.GetCompletions, input, out partial_input);
403        if (parser == null){
404          return null;
405        }
406
407        Class host = parser.InteractiveResult;
408
409        var base_class_imported = importer.ImportType (base_class);
410        var baseclass_list = new List<FullNamedExpression> (1) {
411          new TypeExpression (base_class_imported, host.Location)
412        };
413        host.SetBaseTypes (baseclass_list);
414
415#if NET_4_0
416        var access = AssemblyBuilderAccess.RunAndCollect;
417#else
418        var access = AssemblyBuilderAccess.Run;
419#endif
420        var a = new AssemblyDefinitionDynamic (module, "completions");
421        a.Create (AppDomain.CurrentDomain, access);
422        module.SetDeclaringAssembly (a);
423
424        // Need to setup MemberCache
425        host.CreateContainer ();
426        // Need to setup base type
427        host.DefineContainer ();
428
429        var method = host.Members[0] as Method;
430        BlockContext bc = new BlockContext (method, method.Block, ctx.BuiltinTypes.Void);
431
432        try {
433          method.Block.Resolve (bc, method);
434        } catch (CompletionResult cr) {
435          prefix = cr.BaseText;
436          return cr.Result;
437        }
438      }
439      return null;
440    }
441
442    /// <summary>
443    ///   Executes the given expression or statement.
444    /// </summary>
445    /// <remarks>
446    ///    Executes the provided statement, returns true
447    ///    on success, false on parsing errors.  Exceptions
448    ///    might be thrown by the called code.
449    /// </remarks>
450    public bool Run (string statement)
451    {
452      object result;
453      bool result_set;
454
455      return Evaluate (statement, out result, out result_set) == null;
456    }
457
458    /// <summary>
459    ///   Evaluates and expression or statement and returns the result.
460    /// </summary>
461    /// <remarks>
462    ///   Evaluates the input string as a C# expression or
463    ///   statement and returns the value.   
464    ///
465    ///   This method will throw an exception if there is a syntax error,
466    ///   of if the provided input is not an expression but a statement.
467    /// </remarks>
468    public object Evaluate (string input)
469    {
470      object result;
471      bool result_set;
472     
473      string r = Evaluate (input, out result, out result_set);
474
475      if (r != null)
476        throw new ArgumentException ("Syntax error on input: partial input");
477     
478      if (result_set == false)
479        throw new ArgumentException ("The expression failed to resolve");
480
481      return result;
482    }
483
484    /// <summary>
485    /// Experimental!
486    /// </summary>
487    public ValueModificationHandler ModificationListener { get; set; }
488
489    enum InputKind {
490      EOF,
491      StatementOrExpression,
492      CompilationUnit,
493      Error
494    }
495
496    //
497    // Deambiguates the input string to determine if we
498    // want to process a statement or if we want to
499    // process a compilation unit.
500    //
501    // This is done using a top-down predictive parser,
502    // since the yacc/jay parser can not deambiguage this
503    // without more than one lookahead token.   There are very
504    // few ambiguities.
505    //
506    InputKind ToplevelOrStatement (SeekableStreamReader seekable)
507    {
508      Tokenizer tokenizer = new Tokenizer (seekable, source_file, new ParserSession (), ctx.Report);
509     
510      // Prefer contextual block keywords over identifiers
511      tokenizer.parsing_block++;
512
513      int t = tokenizer.token ();
514      switch (t){
515      case Token.EOF:
516        return InputKind.EOF;
517       
518      // These are toplevels
519      case Token.EXTERN:
520      case Token.OPEN_BRACKET:
521      case Token.ABSTRACT:
522      case Token.CLASS:
523      case Token.ENUM:
524      case Token.INTERFACE:
525      case Token.INTERNAL:
526      case Token.NAMESPACE:
527      case Token.PRIVATE:
528      case Token.PROTECTED:
529      case Token.PUBLIC:
530      case Token.SEALED:
531      case Token.STATIC:
532      case Token.STRUCT:
533        return InputKind.CompilationUnit;
534       
535      // Definitely expression
536      case Token.FIXED:
537      case Token.BOOL:
538      case Token.BYTE:
539      case Token.CHAR:
540      case Token.DECIMAL:
541      case Token.DOUBLE:
542      case Token.FLOAT:
543      case Token.INT:
544      case Token.LONG:
545      case Token.NEW:
546      case Token.OBJECT:
547      case Token.SBYTE:
548      case Token.SHORT:
549      case Token.STRING:
550      case Token.UINT:
551      case Token.ULONG:
552        return InputKind.StatementOrExpression;
553
554      // These need deambiguation help
555      case Token.USING:
556        t = tokenizer.token ();
557        if (t == Token.EOF)
558          return InputKind.EOF;
559
560        if (t == Token.IDENTIFIER)
561          return InputKind.CompilationUnit;
562        return InputKind.StatementOrExpression;
563
564
565      // Distinguish between:
566      //    delegate opt_anonymous_method_signature block
567      //    delegate type
568      case Token.DELEGATE:
569        t = tokenizer.token ();
570        if (t == Token.EOF)
571          return InputKind.EOF;
572        if (t == Token.OPEN_PARENS || t == Token.OPEN_BRACE)
573          return InputKind.StatementOrExpression;
574        return InputKind.CompilationUnit;
575
576      // Distinguih between:
577      //    unsafe block
578      //    unsafe as modifier of a type declaration
579      case Token.UNSAFE:
580        t = tokenizer.token ();
581        if (t == Token.EOF)
582          return InputKind.EOF;
583        if (t == Token.OPEN_PARENS)
584          return InputKind.StatementOrExpression;
585        return InputKind.CompilationUnit;
586       
587            // These are errors: we list explicitly what we had
588      // from the grammar, ERROR and then everything else
589
590      case Token.READONLY:
591      case Token.OVERRIDE:
592      case Token.ERROR:
593        return InputKind.Error;
594
595      // This catches everything else allowed by
596      // expressions.  We could add one-by-one use cases
597      // if needed.
598      default:
599        return InputKind.StatementOrExpression;
600      }
601    }
602   
603    //
604    // Parses the string @input and returns a CSharpParser if succeeful.
605    //
606    // if @silent is set to true then no errors are
607    // reported to the user.  This is used to do various calls to the
608    // parser and check if the expression is parsable.
609    //
610    // @partial_input: if @silent is true, then it returns whether the
611    // parsed expression was partial, and more data is needed
612    //
613    CSharpParser ParseString (ParseMode mode, string input, out bool partial_input)
614    {
615      partial_input = false;
616      Reset ();
617
618      var enc = ctx.Settings.Encoding;
619      var s = new MemoryStream (enc.GetBytes (input));
620      SeekableStreamReader seekable = new SeekableStreamReader (s, enc);
621
622      InputKind kind = ToplevelOrStatement (seekable);
623      if (kind == InputKind.Error){
624        if (mode == ParseMode.ReportErrors)
625          ctx.Report.Error (-25, "Detection Parsing Error");
626        partial_input = false;
627        return null;
628      }
629
630      if (kind == InputKind.EOF){
631        if (mode == ParseMode.ReportErrors)
632          Console.Error.WriteLine ("Internal error: EOF condition should have been detected in a previous call with silent=true");
633        partial_input = true;
634        return null;
635       
636      }
637      seekable.Position = 0;
638
639      source_file.DeclarationFound = false;
640      CSharpParser parser = new CSharpParser (seekable, source_file, new ParserSession ());
641
642      if (kind == InputKind.StatementOrExpression){
643        parser.Lexer.putback_char = Tokenizer.EvalStatementParserCharacter;
644        parser.Lexer.parsing_block++;
645        ctx.Settings.StatementMode = true;
646      } else {
647        parser.Lexer.putback_char = Tokenizer.EvalCompilationUnitParserCharacter;
648        ctx.Settings.StatementMode = false;
649      }
650
651      if (mode == ParseMode.GetCompletions)
652        parser.Lexer.CompleteOnEOF = true;
653
654      ReportPrinter old_printer = null;
655      if ((mode == ParseMode.Silent || mode == ParseMode.GetCompletions))
656        old_printer = ctx.Report.SetPrinter (new StreamReportPrinter (TextWriter.Null));
657
658      try {
659        parser.parse ();
660      } finally {
661        if (ctx.Report.Errors != 0){
662          if (mode != ParseMode.ReportErrors  && parser.UnexpectedEOF)
663            partial_input = true;
664
665          if (parser.undo != null)
666            parser.undo.ExecuteUndo ();
667
668          parser = null;
669        }
670
671        if (old_printer != null)
672          ctx.Report.SetPrinter (old_printer);
673      }
674      return parser;
675    }
676
677    CompiledMethod CompileBlock (Class host, Undo undo, Report Report)
678    {
679#if STATIC
680      throw new NotSupportedException ();
681#else
682      string current_debug_name = "eval-" + count + ".dll";
683      ++count;
684
685      AssemblyDefinitionDynamic assembly;
686      AssemblyBuilderAccess access;
687
688      if (Environment.GetEnvironmentVariable ("SAVE") != null) {
689        access = AssemblyBuilderAccess.RunAndSave;
690        assembly = new AssemblyDefinitionDynamic (module, current_debug_name, current_debug_name);
691        assembly.Importer = importer;
692      } else {
693#if NET_4_0
694        access = AssemblyBuilderAccess.RunAndCollect;
695#else
696        access = AssemblyBuilderAccess.Run;
697#endif
698        assembly = new AssemblyDefinitionDynamic (module, current_debug_name);
699      }
700
701      assembly.Create (AppDomain.CurrentDomain, access);
702
703      Method expression_method;
704      if (host != null) {
705        var base_class_imported = importer.ImportType (base_class);
706        var baseclass_list = new List<FullNamedExpression> (1) {
707          new TypeExpression (base_class_imported, host.Location)
708        };
709
710        host.SetBaseTypes (baseclass_list);
711
712        expression_method = (Method) host.Members[0];
713
714        if ((expression_method.ModFlags & Modifiers.ASYNC) != 0) {
715          //
716          // Host method is async. When WaitOnTask is set we wrap it with wait
717          //
718          // void AsyncWait (ref object $retval) {
719          //  $retval = Host();
720          //  ((Task)$retval).Wait();  // When WaitOnTask is set
721          // }
722          //
723          var p = new ParametersCompiled (
724            new Parameter (new TypeExpression (module.Compiler.BuiltinTypes.Object, Location.Null), "$retval", Parameter.Modifier.REF, null, Location.Null)
725          );
726
727          var method = new Method(host, new TypeExpression(module.Compiler.BuiltinTypes.Void, Location.Null),
728            Modifiers.PUBLIC | Modifiers.STATIC, new MemberName("AsyncWait"), p, null);
729
730          method.Block = new ToplevelBlock(method.Compiler, p, Location.Null);
731          method.Block.AddStatement(new StatementExpression (new SimpleAssign(
732            new SimpleName(p [0].Name, Location.Null),
733            new Invocation(new SimpleName(expression_method.MemberName.Name, Location.Null), new Arguments(0)),
734            Location.Null), Location.Null));
735
736          if (WaitOnTask) {
737            var task = new Cast (expression_method.TypeExpression, new SimpleName (p [0].Name, Location.Null), Location.Null);
738
739            method.Block.AddStatement (new StatementExpression (new Invocation (
740                new MemberAccess (task, "Wait", Location.Null),
741              new Arguments (0)), Location.Null));
742          }
743
744          host.AddMember(method);
745
746          expression_method = method;
747        }
748
749        host.CreateContainer();
750        host.DefineContainer();
751        host.Define();
752
753      } else {
754        expression_method = null;
755      }
756
757      module.CreateContainer ();
758
759      // Disable module and source file re-definition checks
760      module.EnableRedefinition ();
761      source_file.EnableRedefinition ();
762
763      module.Define ();
764
765      if (Report.Errors != 0){
766        if (undo != null)
767          undo.ExecuteUndo ();
768
769        return null;
770      }
771
772      if (host != null){
773        host.PrepareEmit ();
774        host.EmitContainer ();
775      }
776     
777      module.EmitContainer ();
778
779      if (Report.Errors != 0){
780        if (undo != null)
781          undo.ExecuteUndo ();
782        return null;
783      }
784
785      module.CloseContainer ();
786      if (host != null)
787        host.CloseContainer ();
788
789      if (access == AssemblyBuilderAccess.RunAndSave)
790        assembly.Save ();
791
792      if (host == null)
793        return null;
794     
795      //
796      // Unlike Mono, .NET requires that the MethodInfo is fetched, it cant
797      // work from MethodBuilders.   Retarded, I know.
798      //
799      var tt = assembly.Builder.GetType (host.TypeBuilder.Name);
800      var mi = tt.GetMethod (expression_method.MemberName.Name);
801
802      //
803      // We need to then go from FieldBuilder to FieldInfo
804      // or reflection gets confused (it basically gets confused, and variables override each
805      // other).
806      //
807      foreach (var member in host.Members) {
808        var field = member as Field;
809        if (field == null)
810          continue;
811
812        var fi = tt.GetField (field.Name);
813
814        Tuple<FieldSpec, FieldInfo> old;
815
816        // If a previous value was set, nullify it, so that we do
817        // not leak memory
818        if (fields.TryGetValue (field.Name, out old)) {
819          if (old.Item1.MemberType.IsStruct) {
820            //
821            // TODO: Clear fields for structs
822            //
823          } else {
824            try {
825              old.Item2.SetValue (null, null);
826            } catch {
827            }
828          }
829        }
830
831        fields[field.Name] = Tuple.Create (field.Spec, fi);
832      }
833     
834      return (CompiledMethod) System.Delegate.CreateDelegate (typeof (CompiledMethod), mi);
835#endif
836    }
837
838    /// <summary>
839    ///   A sentinel value used to indicate that no value was
840    ///   was set by the compiled function.   This is used to
841    ///   differentiate between a function not returning a
842    ///   value and null.
843    /// </summary>
844    internal static class QuitValue { }
845
846    internal Tuple<FieldSpec, FieldInfo> LookupField (string name)
847    {
848      Tuple<FieldSpec, FieldInfo> fi;
849      fields.TryGetValue (name, out fi);
850      return fi;
851    }
852
853    static string Quote (string s)
854    {
855      if (s.IndexOf ('"') != -1)
856        s = s.Replace ("\"", "\\\"");
857     
858      return "\"" + s + "\"";
859    }
860
861    public string GetUsing ()
862    {
863      StringBuilder sb = new StringBuilder ();
864      // TODO:
865      //foreach (object x in ns.using_alias_list)
866      //    sb.AppendFormat ("using {0};\n", x);
867
868      foreach (var ue in source_file.Usings) {
869        sb.AppendFormat ("using {0};", ue.ToString ());
870        sb.Append (Environment.NewLine);
871      }
872
873      return sb.ToString ();
874    }
875
876    internal List<string> GetUsingList ()
877    {
878      var res = new List<string> ();
879
880      foreach (var ue in source_file.Usings) {
881        if (ue.Alias != null || ue.ResolvedExpression == null)
882          continue;
883
884        res.Add (ue.NamespaceExpression.Name);
885      }
886
887      return res;
888    }
889   
890    internal string [] GetVarNames ()
891    {
892      lock (evaluator_lock){
893        return new List<string> (fields.Keys).ToArray ();
894      }
895    }
896   
897    public string GetVars ()
898    {
899      lock (evaluator_lock){
900        StringBuilder sb = new StringBuilder ();
901       
902        foreach (var de in fields){
903          var fi = LookupField (de.Key);
904          object value;
905          try {
906            value = fi.Item2.GetValue (null);
907            if (value is string)
908              value = Quote ((string)value);
909          } catch {
910            value = "<error reading value>";
911          }
912
913          sb.AppendFormat ("{0} {1} = {2}", fi.Item1.MemberType.GetSignatureForError (), de.Key, value);
914          sb.AppendLine ();
915        }
916       
917        return sb.ToString ();
918      }
919    }
920
921    /// <summary>
922    ///    Loads the given assembly and exposes the API to the user.
923    /// </summary>
924    public void LoadAssembly (string file)
925    {
926      var loader = new DynamicLoader (importer, ctx);
927      var assembly = loader.LoadAssemblyFile (file, false);
928      if (assembly == null)
929        return;
930
931      lock (evaluator_lock){
932        importer.ImportAssembly (assembly, module.GlobalRootNamespace);
933      }
934    }
935
936    /// <summary>
937    ///    Exposes the API of the given assembly to the Evaluator
938    /// </summary>
939    public void ReferenceAssembly (Assembly a)
940    {
941      lock (evaluator_lock){
942        importer.ImportAssembly (a, module.GlobalRootNamespace);
943      }
944    }
945  }
946
947 
948  /// <summary>
949  ///   A delegate that can be used to invoke the
950  ///   compiled expression or statement.
951  /// </summary>
952  /// <remarks>
953  ///   Since the Compile methods will compile
954  ///   statements and expressions into the same
955  ///   delegate, you can tell if a value was returned
956  ///   by checking whether the returned value is of type
957  ///   NoValueSet.   
958  /// </remarks>
959 
960  public delegate void CompiledMethod (ref object retvalue);
961
962  /// <summary>
963  ///   The default base class for every interaction line
964  /// </summary>
965  /// <remarks>
966  ///   The expressions and statements behave as if they were
967  ///   a static method of this class.   The InteractiveBase class
968  ///   contains a number of useful methods, but can be overwritten
969  ///   by setting the InteractiveBaseType property in the Evaluator
970  /// </remarks>
971  public class InteractiveBase {
972    /// <summary>
973    ///   Determines where the standard output of methods in this class will go.
974    /// </summary>
975    public static TextWriter Output = Console.Out;
976
977    /// <summary>
978    ///   Determines where the standard error of methods in this class will go.
979    /// </summary>
980    public static TextWriter Error = Console.Error;
981
982    /// <summary>
983    ///   The primary prompt used for interactive use.
984    /// </summary>
985    public static string Prompt             = "csharp> ";
986
987    /// <summary>
988    ///   The secondary prompt used for interactive use (used when
989    ///   an expression is incomplete).
990    /// </summary>
991    public static string ContinuationPrompt = "      > ";
992
993    /// <summary>
994    ///   Used to signal that the user has invoked the  `quit' statement.
995    /// </summary>
996    public static bool QuitRequested;
997
998    public static Evaluator Evaluator;
999   
1000    /// <summary>
1001    ///   Shows all the variables defined so far.
1002    /// </summary>
1003    static public void ShowVars ()
1004    {
1005      Output.Write (Evaluator.GetVars ());
1006      Output.Flush ();
1007    }
1008
1009    /// <summary>
1010    ///   Displays the using statements in effect at this point.
1011    /// </summary>
1012    static public void ShowUsing ()
1013    {
1014      Output.Write (Evaluator.GetUsing ());
1015      Output.Flush ();
1016    }
1017 
1018    /// <summary>
1019    ///   Times the execution of the given delegate
1020    /// </summary>
1021    static public TimeSpan Time (Action a)
1022    {
1023      DateTime start = DateTime.Now;
1024      a ();
1025      return DateTime.Now - start;
1026    }
1027   
1028    /// <summary>
1029    ///   Loads the assemblies from a package
1030    /// </summary>
1031    /// <remarks>
1032    ///   Loads the assemblies from a package.   This is equivalent
1033    ///   to passing the -pkg: command line flag to the C# compiler
1034    ///   on the command line.
1035    /// </remarks>
1036    static public void LoadPackage (string pkg)
1037    {
1038      if (pkg == null){
1039        Error.WriteLine ("Invalid package specified");
1040        return;
1041      }
1042
1043      string pkgout = Driver.GetPackageFlags (pkg, null);
1044
1045      string [] xargs = pkgout.Trim (new Char [] {' ', '\n', '\r', '\t'}).
1046        Split (new Char [] { ' ', '\t'});
1047
1048      foreach (string s in xargs){
1049        if (s.StartsWith ("-r:") || s.StartsWith ("/r:") || s.StartsWith ("/reference:")){
1050          string lib = s.Substring (s.IndexOf (':')+1);
1051
1052          Evaluator.LoadAssembly (lib);
1053          continue;
1054        }
1055      }
1056    }
1057
1058    /// <summary>
1059    ///   Loads the assembly
1060    /// </summary>
1061    /// <remarks>
1062    ///   Loads the specified assembly and makes its types
1063    ///   available to the evaluator.  This is equivalent
1064    ///   to passing the -pkg: command line flag to the C#
1065    ///   compiler on the command line.
1066    /// </remarks>
1067    static public void LoadAssembly (string assembly)
1068    {
1069      Evaluator.LoadAssembly (assembly);
1070    }
1071
1072    static public void print (object obj)
1073    {
1074      Output.WriteLine (obj);
1075    }
1076
1077    static public void print (string fmt, params object [] args)
1078    {
1079      Output.WriteLine (fmt, args);
1080    }
1081   
1082    /// <summary>
1083    ///   Returns a list of available static methods.
1084    /// </summary>
1085    static public string help {
1086      get {
1087        return "Static methods:\n" +
1088          "  Describe (object);       - Describes the object's type\n" +
1089          "  LoadPackage (package);   - Loads the given Package (like -pkg:FILE)\n" +
1090          "  LoadAssembly (assembly); - Loads the given assembly (like -r:ASSEMBLY)\n" +
1091          "  ShowVars ();             - Shows defined local variables.\n" +
1092          "  ShowUsing ();            - Show active using declarations.\n" +
1093          "  Prompt                   - The prompt used by the C# shell\n" +
1094          "  ContinuationPrompt       - The prompt for partial input\n" +
1095          "  Time (() => { });        - Times the specified code\n" +
1096          "  print (obj);             - Shorthand for Console.WriteLine\n" +
1097          "  quit;                    - You'll never believe it - this quits the repl!\n" +
1098          "  help;                    - This help text\n";
1099      }
1100    }
1101
1102    /// <summary>
1103    ///   Indicates to the read-eval-print-loop that the interaction should be finished.
1104    /// </summary>
1105    static public object quit {
1106      get {
1107        QuitRequested = true;
1108
1109        // To avoid print null at the exit
1110        return typeof (Evaluator.QuitValue);
1111      }
1112    }
1113
1114    /// <summary>
1115    ///   Same as quit - useful in script scenerios
1116    /// </summary>
1117    static public void Quit () {
1118      QuitRequested = true;
1119    }
1120
1121#if !NET_2_1
1122    /// <summary>
1123    ///   Describes an object or a type.
1124    /// </summary>
1125    /// <remarks>
1126    ///   This method will show a textual representation
1127    ///   of the object's type.  If the object is a
1128    ///   System.Type it renders the type directly,
1129    ///   otherwise it renders the type returned by
1130    ///   invoking GetType on the object.
1131    /// </remarks>
1132    static public string Describe (object x)
1133    {
1134      if (x == null)
1135        return "<null>";
1136
1137      var type = x as Type ?? x.GetType ();
1138
1139      StringWriter sw = new StringWriter ();
1140      new Outline (type, sw, true, false, false).OutlineType ();
1141      return sw.ToString ();
1142    }
1143#endif
1144  }
1145
1146  class InteractiveMethod : Method
1147  {
1148    public InteractiveMethod(TypeDefinition parent, FullNamedExpression returnType, Modifiers mod, ParametersCompiled parameters)
1149      : base(parent, returnType, mod, new MemberName("Host"), parameters, null)
1150    {
1151    }
1152
1153    public void ChangeToAsync ()
1154    {
1155      ModFlags |= Modifiers.ASYNC;
1156      ModFlags &= ~Modifiers.UNSAFE;
1157      type_expr = new TypeExpression(Module.PredefinedTypes.Task.TypeSpec, Location);
1158      parameters = ParametersCompiled.EmptyReadOnlyParameters;
1159    }
1160
1161    public override string GetSignatureForError()
1162    {
1163      return "InteractiveHost";
1164    }
1165  }
1166
1167  class HoistedEvaluatorVariable : HoistedVariable
1168  {
1169    public HoistedEvaluatorVariable (Field field)
1170      : base (null, field)
1171    {
1172    }
1173
1174    protected override FieldExpr GetFieldExpression (EmitContext ec)
1175    {
1176      return new FieldExpr (field, field.Location);
1177    }
1178  }
1179
1180  /// <summary>
1181  ///    A class used to assign values if the source expression is not void
1182  ///
1183  ///    Used by the interactive shell to allow it to call this code to set
1184  ///    the return value for an invocation.
1185  /// </summary>
1186  class OptionalAssign : SimpleAssign {
1187    public OptionalAssign (Expression s, Location loc)
1188      : base (null, s, loc)
1189    {
1190    }
1191
1192    public override Location StartLocation {
1193      get {
1194        return Location.Null;
1195      }
1196    }
1197
1198    protected override Expression DoResolve (ResolveContext ec)
1199    {
1200      Expression clone = source.Clone (new CloneContext ());
1201
1202      clone = clone.Resolve (ec);
1203      if (clone == null)
1204        return null;
1205
1206      //
1207      // A useful feature for the REPL: if we can resolve the expression
1208      // as a type, Describe the type;
1209      //
1210      if (ec.Module.Evaluator.DescribeTypeExpressions && !(ec.CurrentAnonymousMethod is AsyncInitializer)) {
1211        var old_printer = ec.Report.SetPrinter (new SessionReportPrinter ());
1212        Expression tclone;
1213        try {
1214          // Note: clone context cannot be shared otherwise block mapping would leak
1215          tclone = source.Clone (new CloneContext ());
1216          tclone = tclone.Resolve (ec, ResolveFlags.Type);
1217          if (ec.Report.Errors > 0)
1218            tclone = null;
1219        } finally {
1220          ec.Report.SetPrinter (old_printer);
1221        }
1222
1223        if (tclone is TypeExpr) {
1224          Arguments args = new Arguments (1);
1225          args.Add (new Argument (new TypeOf ((TypeExpr) clone, Location)));
1226          return new Invocation (new SimpleName ("Describe", Location), args).Resolve (ec);
1227        }
1228      }
1229
1230      // This means its really a statement.
1231      if (clone.Type.Kind == MemberKind.Void || clone is DynamicInvocation || clone is Assign) {
1232        return clone;
1233      }
1234
1235      source = clone;
1236
1237      var host = (Method) ec.MemberContext.CurrentMemberDefinition;
1238
1239      if (host.ParameterInfo.IsEmpty) {
1240        eclass = ExprClass.Value;
1241        type = InternalType.FakeInternalType;
1242        return this;
1243      }
1244
1245      target = new SimpleName (host.ParameterInfo[0].Name, Location);
1246
1247      return base.DoResolve (ec);
1248    }
1249
1250    public override void EmitStatement(EmitContext ec)
1251    {
1252      if (target == null) {
1253        source.Emit (ec);
1254        return;
1255      }
1256
1257      base.EmitStatement(ec);
1258    }
1259  }
1260
1261  public class Undo
1262  {
1263    List<Action> undo_actions;
1264
1265    public void AddTypeContainer (TypeContainer current_container, TypeDefinition tc)
1266    {
1267      if (current_container == tc){
1268        Console.Error.WriteLine ("Internal error: inserting container into itself");
1269        return;
1270      }
1271
1272      if (undo_actions == null)
1273        undo_actions = new List<Action> ();
1274
1275      if (current_container.Containers != null)
1276      {
1277        var existing = current_container.Containers.FirstOrDefault (l => l.Basename == tc.Basename);
1278        if (existing != null) {
1279          current_container.RemoveContainer (existing);
1280          undo_actions.Add (() => current_container.AddTypeContainer (existing));
1281        }
1282      }
1283
1284      undo_actions.Add (() => current_container.RemoveContainer (tc));
1285    }
1286
1287    public void ExecuteUndo ()
1288    {
1289      if (undo_actions == null)
1290        return;
1291
1292      foreach (var p in undo_actions){
1293        p ();
1294      }
1295
1296      undo_actions = null;
1297    }
1298  }
1299
1300  static class ListenerProxy
1301  {
1302    static readonly Dictionary<int, ValueModificationHandler> listeners = new Dictionary<int, ValueModificationHandler> ();
1303
1304    static int counter;
1305
1306    public static int Register (ValueModificationHandler listener)
1307    {
1308      lock (listeners) {
1309        var id = counter++;
1310        listeners.Add (id, listener);
1311        return id;
1312      }
1313    }
1314
1315    public static void Unregister (int listenerId)
1316    {
1317      lock (listeners) {
1318        listeners.Remove (listenerId);
1319      }
1320    }
1321
1322    public static void ValueChanged (object value, int row, int col, string name, int listenerId)
1323    {
1324      ValueModificationHandler action;
1325      lock (listeners) {
1326        if (!listeners.TryGetValue (listenerId, out action))
1327          return;
1328      }
1329
1330      action (name, row, col, value);
1331    }
1332  }
1333}
Note: See TracBrowser for help on using the repository browser.