//
using System;
using System.Threading;
-using System.Collections;
+using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.IO;
/// </remarks>
public class Evaluator {
+ enum ParseMode {
+ // Parse silently, do not output any error messages
+ Silent,
+
+ // Report errors during parse
+ ReportErrors,
+
+ // Auto-complete, means that the tokenizer will start producing
+ // GETCOMPLETIONS tokens when it reaches a certain point.
+ GetCompletions
+ }
+
static object evaluator_lock = new object ();
static string current_debug_name;
static int count;
static Thread invoke_thread;
-
- static ArrayList using_alias_list = new ArrayList ();
- static ArrayList using_list = new ArrayList ();
- static Hashtable fields = new Hashtable ();
- static Type interactive_base_class = typeof (InteractiveBase);
+ static List<NamespaceEntry.UsingAliasEntry> using_alias_list = new List<NamespaceEntry.UsingAliasEntry> ();
+ internal static List<NamespaceEntry.UsingEntry> using_list = new List<NamespaceEntry.UsingEntry> ();
+ static Dictionary<string, Tuple<FieldSpec, FieldInfo>> fields = new Dictionary<string, Tuple<FieldSpec, FieldInfo>> ();
+
+ static TypeSpec interactive_base_class;
static Driver driver;
static bool inited;
+ static CompilerContext ctx;
+
+ public static TextWriter MessageOutput = Console.Out;
+
/// <summary>
/// Optional initialization for the Evaluator.
/// </summary>
/// interface.
/// </remarks>
public static void Init (string [] args)
+ {
+ InitAndGetStartupFiles (args);
+ }
+
+ internal static ReportPrinter SetPrinter (ReportPrinter report_printer)
+ {
+ return ctx.Report.SetPrinter (report_printer);
+ }
+
+ /// <summary>
+ /// Optional initialization for the Evaluator.
+ /// </summary>
+ /// <remarks>
+ /// Initializes the Evaluator with the command line
+ /// options that would be processed by the command
+ /// line compiler. Only the first call to
+ /// InitAndGetStartupFiles or Init will work, any future
+ /// invocations are ignored.
+ ///
+ /// You can safely avoid calling this method if your application
+ /// does not need any of the features exposed by the command line
+ /// interface.
+ ///
+ /// This method return an array of strings that contains any
+ /// files that were specified in `args'.
+ /// </remarks>
+ public static string [] InitAndGetStartupFiles (string [] args)
{
lock (evaluator_lock){
if (inited)
- return;
+ return new string [0];
- RootContext.Version = LanguageVersion.Default;
- driver = Driver.Create (args, false);
+ driver = Driver.Create (args, false, new ConsoleReportPrinter ());
if (driver == null)
throw new Exception ("Failed to create compiler driver with the given arguments");
+
+ RootContext.ToplevelTypes = new ModuleCompiled (ctx, true);
driver.ProcessDefaultConfig ();
+
+ var startup_files = new List<string> ();
+ foreach (CompilationUnit file in Location.SourceFiles)
+ startup_files.Add (file.Path);
+
CompilerCallableEntryPoint.Reset ();
- Driver.LoadReferences ();
+ RootContext.ToplevelTypes = new ModuleCompiled (ctx, true);
+ /*var ctypes = */TypeManager.InitCoreTypes ();
+
+ Import.Initialize ();
+ driver.LoadReferences ();
+ TypeManager.InitOptionalCoreTypes (ctx);
+
RootContext.EvalMode = true;
inited = true;
+
+ return startup_files.ToArray ();
}
}
static void Reset ()
{
CompilerCallableEntryPoint.PartialReset ();
+ RootContext.PartialReset ();
+
+ // Workaround for API limitation where full message printer cannot be passed
+ ReportPrinter printer;
+ if (MessageOutput == Console.Out || MessageOutput == Console.Error){
+ var console_reporter = new ConsoleReportPrinter (MessageOutput);
+ console_reporter.Fatal = driver.fatal_errors;
+ printer = console_reporter;
+ } else
+ printer = new StreamReportPrinter (MessageOutput);
+
+ ctx = new CompilerContext (new Report (printer));
+ RootContext.ToplevelTypes = new ModuleCompiled (ctx, true);
//
// PartialReset should not reset the core types, this is very redundant.
//
- if (!TypeManager.InitCoreTypes ())
- throw new Exception ("Failed to InitCoreTypes");
- TypeManager.InitOptionalCoreTypes ();
+// if (!TypeManager.InitCoreTypes (ctx, null))
+// throw new Exception ("Failed to InitCoreTypes");
+// TypeManager.InitOptionalCoreTypes (ctx);
- Location.AddFile ("{interactive}");
+ Location.AddFile (null, "{interactive}");
Location.Initialize ();
current_debug_name = "interactive" + (count++) + ".dll";
if (Environment.GetEnvironmentVariable ("SAVE") != null){
- CodeGen.Init (current_debug_name, current_debug_name, false);
+ CodeGen.Init (current_debug_name, current_debug_name, false, ctx);
} else
- CodeGen.InitDynamic (current_debug_name);
+ CodeGen.InitDynamic (ctx, current_debug_name);
}
/// <summary>
/// base class and the static members that are
/// available to your evaluated code.
/// </remarks>
- static public Type InteractiveBaseClass {
+ static public TypeSpec InteractiveBaseClass {
get {
- return interactive_base_class;
+ if (interactive_base_class != null)
+ return interactive_base_class;
+
+ return Import.ImportType (typeof (InteractiveBase));
}
+ }
- set {
- if (value == null)
- throw new ArgumentNullException ();
+ public static void SetInteractiveBaseClass (Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException ();
- lock (evaluator_lock)
- interactive_base_class = value;
- }
+ lock (evaluator_lock)
+ interactive_base_class = Import.ImportType (type);
}
/// <summary>
Init ();
bool partial_input;
- CSharpParser parser = ParseString (true, input, out partial_input);
+ CSharpParser parser = ParseString (ParseMode.Silent, input, out partial_input);
if (parser == null){
compiled = null;
if (partial_input)
return input;
- ParseString (false, input, out partial_input);
+ ParseString (ParseMode.ReportErrors, input, out partial_input);
return null;
}
object parser_result = parser.InteractiveResult;
- if (!(parser_result is Class))
- parser.CurrentNamespace.Extract (using_alias_list, using_list);
+ if (!(parser_result is Class)){
+ int errors = ctx.Report.Errors;
+
+ NamespaceEntry.VerifyAllUsing ();
+ if (errors == ctx.Report.Errors)
+ parser.CurrentNamespace.Extract (using_alias_list, using_list);
+ }
- compiled = CompileBlock (parser_result as Class, parser.undo);
+ compiled = CompileBlock (parser_result as Class, parser.undo, ctx.Report);
}
return null;
// Either null (on error) or the compiled method.
return compiled;
}
-
+
//
// Todo: Should we handle errors, or expect the calling code to setup
// the recording themselves?
// The code execution does not need to keep the compiler lock
//
object retval = typeof (NoValueSet);
-
+
try {
invoke_thread = System.Threading.Thread.CurrentThread;
invoking = true;
return null;
}
+
+ public static string [] GetCompletions (string input, out string prefix)
+ {
+ prefix = "";
+ if (input == null || input.Length == 0)
+ return null;
+ lock (evaluator_lock){
+ if (!inited)
+ Init ();
+
+ bool partial_input;
+ CSharpParser parser = ParseString (ParseMode.GetCompletions, input, out partial_input);
+ if (parser == null){
+ if (CSharpParser.yacc_verbose_flag != 0)
+ Console.WriteLine ("DEBUG: No completions available");
+ return null;
+ }
+
+ Class parser_result = parser.InteractiveResult as Class;
+
+ if (parser_result == null){
+ if (CSharpParser.yacc_verbose_flag != 0)
+ Console.WriteLine ("Do not know how to cope with !Class yet");
+ return null;
+ }
+
+ try {
+ RootContext.ResolveTree ();
+ if (ctx.Report.Errors != 0)
+ return null;
+
+ RootContext.PopulateTypes ();
+ if (ctx.Report.Errors != 0)
+ return null;
+
+ MethodOrOperator method = null;
+ foreach (MemberCore member in parser_result.Methods){
+ if (member.Name != "Host")
+ continue;
+
+ method = (MethodOrOperator) member;
+ break;
+ }
+ if (method == null)
+ throw new InternalErrorException ("did not find the the Host method");
+
+ BlockContext bc = new BlockContext (method, method.Block, method.ReturnType);
+
+ try {
+ method.Block.Resolve (null, bc, method.ParameterInfo, method);
+ } catch (CompletionResult cr){
+ prefix = cr.BaseText;
+ return cr.Result;
+ }
+ } finally {
+ parser.undo.ExecuteUndo ();
+ }
+
+ }
+ return null;
+ }
+
/// <summary>
/// Executes the given expression or statement.
/// </summary>
return result;
}
-
+
enum InputKind {
EOF,
StatementOrExpression,
//
static InputKind ToplevelOrStatement (SeekableStreamReader seekable)
{
- Tokenizer tokenizer = new Tokenizer (seekable, Location.SourceFiles [0]);
+ Tokenizer tokenizer = new Tokenizer (seekable, (CompilationUnit) Location.SourceFiles [0], ctx);
int t = tokenizer.token ();
switch (t){
// @partial_input: if @silent is true, then it returns whether the
// parsed expression was partial, and more data is needed
//
- static CSharpParser ParseString (bool silent, string input, out bool partial_input)
+ static CSharpParser ParseString (ParseMode mode, string input, out bool partial_input)
{
partial_input = false;
Reset ();
queued_fields.Clear ();
+ Tokenizer.LocatedToken.Initialize ();
Stream s = new MemoryStream (Encoding.Default.GetBytes (input));
SeekableStreamReader seekable = new SeekableStreamReader (s, Encoding.Default);
InputKind kind = ToplevelOrStatement (seekable);
if (kind == InputKind.Error){
- if (!silent)
- Report.Error (-25, "Detection Parsing Error");
+ if (mode == ParseMode.ReportErrors)
+ ctx.Report.Error (-25, "Detection Parsing Error");
partial_input = false;
return null;
}
if (kind == InputKind.EOF){
- if (silent == false)
+ if (mode == ParseMode.ReportErrors)
Console.Error.WriteLine ("Internal error: EOF condition should have been detected in a previous call with silent=true");
partial_input = true;
return null;
}
seekable.Position = 0;
- CSharpParser parser = new CSharpParser (seekable, Location.SourceFiles [0]);
- parser.ErrorOutput = Report.Stderr;
+ CSharpParser parser = new CSharpParser (seekable, (CompilationUnit) Location.SourceFiles [0], ctx);
if (kind == InputKind.StatementOrExpression){
parser.Lexer.putback_char = Tokenizer.EvalStatementParserCharacter;
RootContext.StatementMode = false;
}
- if (silent)
- Report.DisableReporting ();
+ if (mode == ParseMode.GetCompletions)
+ parser.Lexer.CompleteOnEOF = true;
+
+ ReportPrinter old_printer = null;
+ if ((mode == ParseMode.Silent || mode == ParseMode.GetCompletions) && CSharpParser.yacc_verbose_flag == 0)
+ old_printer = SetPrinter (new StreamReportPrinter (TextWriter.Null));
+
try {
parser.parse ();
} finally {
- if (Report.Errors != 0){
- if (silent && parser.UnexpectedEOF)
+ if (ctx.Report.Errors != 0){
+ if (mode != ParseMode.ReportErrors && parser.UnexpectedEOF)
partial_input = true;
parser.undo.ExecuteUndo ();
parser = null;
}
- if (silent)
- Report.EnableReporting ();
+ if (old_printer != null)
+ SetPrinter (old_printer);
}
return parser;
}
// or reflection gets confused (it basically gets confused, and variables override each
// other).
//
- static ArrayList queued_fields = new ArrayList ();
+ static List<Field> queued_fields = new List<Field> ();
//static ArrayList types = new ArrayList ();
static volatile bool invoking;
- static CompiledMethod CompileBlock (Class host, Undo undo)
+ static CompiledMethod CompileBlock (Class host, Undo undo, Report Report)
{
RootContext.ResolveTree ();
if (Report.Errors != 0){
}
RootContext.EmitCode ();
- if (Report.Errors != 0)
+ if (Report.Errors != 0){
+ undo.ExecuteUndo ();
return null;
+ }
RootContext.CloseTypes ();
if (Environment.GetEnvironmentVariable ("SAVE") != null)
- CodeGen.Save (current_debug_name, false);
+ CodeGen.Save (current_debug_name, false, Report);
if (host == null)
return null;
// Unlike Mono, .NET requires that the MethodInfo is fetched, it cant
// work from MethodBuilders. Retarded, I know.
//
- Type tt = CodeGen.Assembly.Builder.GetType (tb.Name);
+ var tt = CodeGen.Assembly.Builder.GetType (tb.Name);
MethodInfo mi = tt.GetMethod (mb.Name);
// Pull the FieldInfos from the type, and keep track of them
foreach (Field field in queued_fields){
FieldInfo fi = tt.GetField (field.Name);
-
- FieldInfo old = (FieldInfo) fields [field.Name];
+
+ Tuple<FieldSpec, FieldInfo> old;
// If a previous value was set, nullify it, so that we do
// not leak memory
- if (old != null){
- if (old.FieldType.IsValueType){
+ if (fields.TryGetValue (field.Name, out old)) {
+ if (old.Item1.MemberType.IsStruct) {
//
// TODO: Clear fields for structs
//
} else {
try {
- old.SetValue (null, null);
+ old.Item2.SetValue (null, null);
} catch {
}
}
+
+ fields [field.Name] = Tuple.Create (old.Item1, fi);
+ } else {
+ fields.Add (field.Name, Tuple.Create (field.Spec, fi));
}
-
- fields [field.Name] = fi;
}
//types.Add (tb);
public class NoValueSet {
}
- static internal FieldInfo LookupField (string name)
+ static internal Tuple<FieldSpec, FieldInfo> LookupField (string name)
{
- FieldInfo fi = (FieldInfo) fields [name];
-
+ Tuple<FieldSpec, FieldInfo> fi;
+ fields.TryGetValue (name, out fi);
return fi;
}
}
}
+ static internal ICollection<string> GetUsingList ()
+ {
+ var res = new List<string> (using_list.Count);
+ foreach (object ue in using_list)
+ res.Add (ue.ToString ());
+ return res;
+ }
+
+ static internal string [] GetVarNames ()
+ {
+ lock (evaluator_lock){
+ return new List<string> (fields.Keys).ToArray ();
+ }
+ }
+
static public string GetVars ()
{
lock (evaluator_lock){
StringBuilder sb = new StringBuilder ();
- foreach (DictionaryEntry de in fields){
- FieldInfo fi = LookupField ((string) de.Key);
- object value = null;
- bool error = false;
-
+ foreach (var de in fields){
+ var fi = LookupField (de.Key);
+ object value;
try {
- if (value == null)
- value = "null";
- value = fi.GetValue (null);
+ value = fi.Item2.GetValue (null);
if (value is string)
value = Quote ((string)value);
} catch {
- error = true;
+ value = "<error reading value>";
}
-
- if (error)
- sb.Append (String.Format ("{0} {1} <error reading value>", TypeManager.CSharpName(fi.FieldType), de.Key));
- else
- sb.Append (String.Format ("{0} {1} = {2}", TypeManager.CSharpName(fi.FieldType), de.Key, value));
+
+ sb.AppendFormat ("{0} {1} = {2}", fi.Item1.MemberType.GetSignatureForError (), de.Key, value);
+ sb.AppendLine ();
}
return sb.ToString ();
static public void LoadAssembly (string file)
{
lock (evaluator_lock){
- Driver.LoadAssembly (file, true);
- RootNamespace.ComputeNamespaces ();
+ driver.LoadAssembly (file, false);
+ GlobalRootNamespace.Instance.ComputeNamespaces (ctx);
}
}
static public void ReferenceAssembly (Assembly a)
{
lock (evaluator_lock){
- RootNamespace.Global.AddAssemblyReference (a);
- RootNamespace.ComputeNamespaces ();
+// GlobalRootNamespace.Instance.AddAssemblyReference (a);
+// GlobalRootNamespace.Instance.ComputeNamespaces (ctx);
+ GlobalRootNamespace.Instance.ImportAssembly (a);
}
}
-
+
+ /// <summary>
+ /// If true, turns type expressions into valid expressions
+ /// and calls the describe method on it
+ /// </summary>
+ public static bool DescribeTypeExpressions;
}
/// </remarks>
public delegate void CompiledMethod (ref object retvalue);
+
/// <summary>
/// The default base class for every interaction line
/// </summary>
+ /// <remarks>
+ /// The expressions and statements behave as if they were
+ /// a static method of this class. The InteractiveBase class
+ /// contains a number of useful methods, but can be overwritten
+ /// by setting the InteractiveBaseType property in the Evaluator
+ /// </remarks>
public class InteractiveBase {
+ /// <summary>
+ /// Determines where the standard output of methods in this class will go.
+ /// </summary>
public static TextWriter Output = Console.Out;
+
+ /// <summary>
+ /// Determines where the standard error of methods in this class will go.
+ /// </summary>
public static TextWriter Error = Console.Error;
+
+ /// <summary>
+ /// The primary prompt used for interactive use.
+ /// </summary>
public static string Prompt = "csharp> ";
+
+ /// <summary>
+ /// The secondary prompt used for interactive use (used when
+ /// an expression is incomplete).
+ /// </summary>
public static string ContinuationPrompt = " > ";
+
+ /// <summary>
+ /// Used to signal that the user has invoked the `quit' statement.
+ /// </summary>
public static bool QuitRequested;
+ /// <summary>
+ /// Shows all the variables defined so far.
+ /// </summary>
static public void ShowVars ()
{
Output.Write (Evaluator.GetVars ());
Output.Flush ();
}
+ /// <summary>
+ /// Displays the using statements in effect at this point.
+ /// </summary>
static public void ShowUsing ()
{
Output.Write (Evaluator.GetUsing ());
public delegate void Simple ();
+ /// <summary>
+ /// Times the execution of the given delegate
+ /// </summary>
static public TimeSpan Time (Simple a)
{
DateTime start = DateTime.Now;
}
#if !SMCS_SOURCE
+ /// <summary>
+ /// Loads the assemblies from a package
+ /// </summary>
+ /// <remarks>
+ /// Loads the assemblies from a package. This is equivalent
+ /// to passing the -pkg: command line flag to the C# compiler
+ /// on the command line.
+ /// </remarks>
static public void LoadPackage (string pkg)
{
if (pkg == null){
return;
}
- string pkgout = Driver.GetPackageFlags (pkg, false);
+ string pkgout = Driver.GetPackageFlags (pkg, false, RootContext.ToplevelTypes.Compiler.Report);
if (pkgout == null)
return;
}
#endif
+ /// <summary>
+ /// Loads the assembly
+ /// </summary>
+ /// <remarks>
+ /// Loads the specified assembly and makes its types
+ /// available to the evaluator. This is equivalent
+ /// to passing the -pkg: command line flag to the C#
+ /// compiler on the command line.
+ /// </remarks>
static public void LoadAssembly (string assembly)
{
Evaluator.LoadAssembly (assembly);
}
+ /// <summary>
+ /// Returns a list of available static methods.
+ /// </summary>
static public string help {
get {
- return "Static methods:\n"+
- " Describe(obj) - Describes the object's type\n" +
- " LoadPackage (pkg); - Loads the given Package (like -pkg:FILE)\n" +
- " LoadAssembly (ass) - Loads the given assembly (like -r:ASS)\n" +
- " ShowVars (); - Shows defined local variables.\n" +
- " ShowUsing (); - Show active using decltions.\n" +
- " Prompt - The prompt used by the C# shell\n" +
- " ContinuationPrompt - The prompt for partial input\n" +
- " Time(() -> { }) - Times the specified code\n" +
- " quit;\n" +
- " help;\n";
+ return "Static methods:\n" +
+ " Describe (object) - Describes the object's type\n" +
+ " LoadPackage (package); - Loads the given Package (like -pkg:FILE)\n" +
+ " LoadAssembly (assembly) - Loads the given assembly (like -r:ASSEMBLY)\n" +
+ " ShowVars (); - Shows defined local variables.\n" +
+ " ShowUsing (); - Show active using declarations.\n" +
+ " Prompt - The prompt used by the C# shell\n" +
+ " ContinuationPrompt - The prompt for partial input\n" +
+ " Time(() -> { }) - Times the specified code\n" +
+ " quit; - You'll never believe it - this quits the repl!\n" +
+ " help; - This help text\n";
}
}
+ /// <summary>
+ /// Indicates to the read-eval-print-loop that the interaction should be finished.
+ /// </summary>
static public object quit {
get {
QuitRequested = true;
}
#if !NET_2_1
+ /// <summary>
+ /// Describes an object or a type.
+ /// </summary>
+ /// <remarks>
+ /// This method will show a textual representation
+ /// of the object's type. If the object is a
+ /// System.Type it renders the type directly,
+ /// otherwise it renders the type returned by
+ /// invoking GetType on the object.
+ /// </remarks>
static public string Describe (object x)
{
if (x == null)
- return "";
-
- Type t = x as Type;
- if (t == null)
- t = x.GetType ();
+ return "<null>";
+
+ var type = x as Type ?? x.GetType ();
StringWriter sw = new StringWriter ();
- new Outline (t, sw, true, false, false).OutlineType ();
+ new Outline (type, sw, true, false, false).OutlineType ();
return sw.ToString ();
}
#endif
TypeContainer container;
string name;
- public LocalVariableReferenceWithClassSideEffect (TypeContainer container, string name, Block current_block, string local_variable_id, Location loc)
- : base (current_block, local_variable_id, loc)
+ public LocalVariableReferenceWithClassSideEffect (TypeContainer container, string name, Block current_block, string local_variable_id, LocalInfo li, Location loc)
+ : base (current_block, local_variable_id, loc, li, false)
{
this.container = container;
this.name = name;
return name.GetHashCode ();
}
- override public Expression DoResolveLValue (EmitContext ec, Expression right_side)
+ override public Expression DoResolveLValue (ResolveContext ec, Expression right_side)
{
Expression ret = base.DoResolveLValue (ec, right_side);
if (ret == null)
{
}
- public override Expression DoResolve (EmitContext ec)
+ protected override Expression DoResolve (ResolveContext ec)
{
CloneContext cc = new CloneContext ();
Expression clone = source.Clone (cc);
- clone = clone.Resolve (ec);
- if (clone == null)
- return null;
-
+ //
+ // A useful feature for the REPL: if we can resolve the expression
+ // as a type, Describe the type;
+ //
+ if (Evaluator.DescribeTypeExpressions){
+ var old_printer = Evaluator.SetPrinter (new StreamReportPrinter (TextWriter.Null));
+ clone = clone.Resolve (ec);
+ if (clone == null){
+ clone = source.Clone (cc);
+ clone = clone.Resolve (ec, ResolveFlags.Type);
+ if (clone == null){
+ Evaluator.SetPrinter (old_printer);
+ clone = source.Clone (cc);
+ clone = clone.Resolve (ec);
+ return null;
+ }
+
+ Arguments args = new Arguments (1);
+ args.Add (new Argument (new TypeOf ((TypeExpr) clone, Location)));
+ source = new Invocation (new SimpleName ("Describe", Location), args).Resolve (ec);
+ }
+ Evaluator.SetPrinter (old_printer);
+ } else {
+ clone = clone.Resolve (ec);
+ if (clone == null)
+ return null;
+ }
+
// This means its really a statement.
if (clone.Type == TypeManager.void_type){
source = source.Resolve (ec);
}
public class Undo {
- ArrayList undo_types;
+ List<KeyValuePair<TypeContainer, TypeContainer>> undo_types;
public Undo ()
{
- undo_types = new ArrayList ();
+ undo_types = new List<KeyValuePair<TypeContainer, TypeContainer>> ();
}
public void AddTypeContainer (TypeContainer current_container, TypeContainer tc)
Console.Error.WriteLine ("Internal error: inserting container into itself");
return;
}
-
+
if (undo_types == null)
- undo_types = new ArrayList ();
- undo_types.Add (new Pair (current_container, tc));
+ undo_types = new List<KeyValuePair<TypeContainer, TypeContainer>> ();
+
+ undo_types.Add (new KeyValuePair<TypeContainer, TypeContainer> (current_container, tc));
}
public void ExecuteUndo ()
if (undo_types == null)
return;
- foreach (Pair p in undo_types){
- TypeContainer current_container = (TypeContainer) p.First;
+ foreach (var p in undo_types){
+ TypeContainer current_container = p.Key;
- current_container.RemoveTypeContainer ((TypeContainer) p.Second);
+ current_container.RemoveTypeContainer (p.Value);
}
undo_types = null;
}