using Nemerle.Collections; using Nemerle.Compiler; using Nemerle.Compiler.Parsetree; using Nemerle.Text; using Nemerle.Utility; [assembly: System.Reflection.AssemblyTitle("Nemerle Evaluation Library")] [assembly: System.Reflection.AssemblyDescription("Nemerle (http://nemerle.org) Runtime evaluation library")] [assembly: System.Reflection.AssemblyCompany("University of Wroclaw")] [assembly: System.Reflection.AssemblyProduct("Nemerle Evaluation Library")] [assembly: System.Reflection.AssemblyCopyright("Copyright @ University of Wroclaw 2005-2008")] [assembly: Nemerle.Utility.AssemblyVersionFromSVN("0.9.4.SVN")] [assembly: System.Runtime.InteropServices.ComVisible (false)] namespace Nemerle.Evaluation { /** * Allows dynamic evaluation of code, with persistent computation * history (retaining computed variables, functions, etc.) between * subsequent calls to Evaluator ().Eval (). * * * NemerleCodeProvider * */ public class Evaluator : ManagerClass { // Evaluated code. static mutable code : string; // Whether the code compiled successfully. mutable static compiled : bool; // [(is_mutable, name, type)] static mutable locals : list [bool * string * PExpr] = []; // The new values returned after evaluation. This // was made a field because it's in a try block. mutable static newvals : list [object] = []; // A list of namespace aliases and namespaces that are to be opened. // [(Some (shortname), longname) or (None (), longname)] public static mutable ns : list [option [string] * list [string]] = []; // A list of assemblies to reference. public static mutable refr : list [string] = []; // [value] public static mutable vals : list [object] = []; public static mutable firstRun : bool = true; private persistentMode : bool; public this (path : string) { this (true, path); } public this (persistent_mode : bool, path : string = null) { base (CompilationOptions ()); this.persistentMode = persistent_mode; this.InitOutput (System.Console.Out); when (path != null) { // We need to add the application directory to the search path, so // the assemblies in nemish.exe directory can be loaded. Options.LibraryPaths ::= path; } Options.CompileToMemory = true; Options.IgnoreConfusion = true; Options.ProgressBar = false; Options.Sources = [""]; // HACK! HACK! HACK! // This is broken, broken, broken! The values here shouldn't be // static and there should be no need to clear them. // HACK! HACK! HACK! locals = []; newvals = []; vals = []; refr = []; firstRun = true; code = null; compiled = false; ns = []; } public static EvaluateExpression ([Nemerle.Assertions.NotNull] code : string) : object { def eval = Evaluator (false, null); match (eval.Eval (code)) { | [] => null | returned => returned.Last [3]; } } /** * Evaluates supplied code in memory and returns computation results. * Persistent computation history is maintained between subsequent calls. * * * The code to evaluate. * * * A sorted list of computed variables and functions with additional * descriptive information. The tuple members from the list are: * #1 - true if the variable was introduced during the last Eval, * false if it was on the list before. * #2 - true if the variable is mutable, false if not. * #3 - name of the variable. * #4 - value of the variable. * #5 - type of the variable. * * The last computed value - the return value of the evaluated code * is returned as a special variable named "it". * */ public Eval (code: string) : list [bool * bool * string * object * PExpr] { Evaluator.code = code; this.LexingPipeline = fun (_) { null }; this.ParsingPipeline = fun (_) { [null] }; this.ScanningPipeline = DoTheStuff; // Options.DumpTypedTree = true; // Options.AdditionalDebug = true; Options.PersistentLibraries = this.persistentMode && !firstRun; // Link ourselves. Options.ReferencedLibraries = System.Uri(this.GetType().Assembly.CodeBase).LocalPath :: Evaluator.refr; // unused variable Options.Warnings.Disable (168); // ignore computed value Options.Warnings.Disable (10005); // Locals and vals from previous calls to Eval. def prevlocals = Evaluator.locals; def prevvals = Evaluator.vals; try { this.Run (); def ass = this.GeneratedAssembly; def meth = ass.GetTypes () [0].GetMethod ("Run"); // And here are the new values (along with the old ones). newvals = meth.Invoke (null, null) :> list [object]; Evaluator.compiled = true } catch { | e => match (e) { | _ is AssertionException | _ is Recovery | _ is System.ApplicationException => if (e.Source == "Nemerle.Compiler" || e.Source == "Nemerle.Evaluation") { Evaluator.locals = prevlocals; Evaluator.newvals = List.Rev (prevvals); Evaluator.compiled = false; unless (e.Message == "Nothing to parse.") System.Console.WriteLine ($"Warning: $e"); } else throw; | _ => throw } } // Check which variables are new to this call and set the first field // in the return tuple - true for variable "it", new ones and those // whose values and/or type has changed; false - for the rest. def prev = Hashtable (); List.Iter2 (prevlocals, prevvals.Rev (), fun ((mut, name, _), val) { prev [name] = (mut, val) }); Evaluator.vals = List.Rev (newvals); List.Map2 (locals, newvals, fun ((mut, name, ty), val) { def is_new = if (prev.Contains (name)) { def (prevmut, prevval) = prev [name]; (name == "it" && compiled == true) || (val != null && ! (val.Equals (prevval))) || (val == null && prevval != null) || prevmut != mut } else true; (is_new, mut, name, val, ty) }).Rev () } // Does the same that Eval does, but instead of evaluation the result // provides a list of possible completion types and members mutable static IsCompletionMode : bool = false; // commented out until the new completion engine in finished /*public Complete (code: string) : Nemerle.Completion.CompletionInfo { IsCompletionMode = true; Evaluator.code = code; this.InitOutput (System.IO.StreamWriter (System.IO.MemoryStream ())); mutable completionList : Nemerle.Completion.CompletionInfo = null; //Nemerle.Completion.Engine.LesserInit(); def lexer = LexerString (this, code, Location (0, 1, 1)); this.Hierarchy = TypesManager(this); this.ParsingPipeline = MainParser.Parse; this.ScanningPipeline = ScanTypeHierarchy (this).ProcessDeclaration; def decls = this.ParsingPipeline (lexer); List.Iter (decls, this.ScanningPipeline); // Link ourselves. Options.ReferencedLibraries = System.Reflection.Assembly.GetAssembly (this.GetType ()).Location :: Evaluator.refr; // Locals and vals from previous calls to Eval. def prevlocals = Evaluator.locals; def prevvals = Evaluator.vals; this.Hierarchy.Run (); try { mutable my_method = null; this.Hierarchy.infos.Iter (fun (ti) { def members = ti.GetMethods(); members.Iter ( fun (member : IMethod) { when (member.Name == "Main") { my_method = member :> MethodBuilder; } } ); } ); def lexer = LexerCompletion (this, Evaluator.code + " ", Evaluator.code.Length); my_method.GetHeader ().body = FunBody.Parsed (MainParser.ParseExpr (my_method.DeclaringType.GlobalEnv, lexer)); my_method.RunBodyTyper (); Evaluator.compiled = false } catch { | e => match (e) { | cr is Nemerle.Compiler.CompletionResult => () //completionList = Nemerle.Completion.Engine.translate_ovpossibility_to_info (cr.Overloads); | _ is AssertionException | _ is Recovery | _ is System.ApplicationException => if (e.Source == "Nemerle.Compiler" || e.Source == "Nemerle.Evaluation") { Evaluator.locals = prevlocals; Evaluator.newvals = List.Rev (prevvals); Evaluator.compiled = false; //System.Console.WriteLine (e); } else throw; | _ => throw } } this.InitOutput (System.Console.Out); this.ParsingPipeline = fun (_) { [null] }; this.ScanningPipeline = DoTheStuff; IsCompletionMode = false; completionList }*/ internal DoTheStuff (_tdecl : Parsetree.TopDeclaration) : void { when (!Options.PersistentLibraries || firstRun) this.MacrosRegistry.RegisterMacro (StagedMacro ()); firstRun = false; // Open namespaces and set aliases. def env = List.FoldLeft (Evaluator.ns, this.CoreEnv, fun (x : option [string] * list [string], acc : GlobalEnv) { match (x [0]) { | None => acc.AddOpenNamespace (x [1], Location.Default) | Some (sname) => acc.AddNamespaceAlias (sname, x [1], Location.Default) } }); MacroColors.PushNewColor (-1, env); // Set the class in which we're going to put the evaluated code. def cname = Macros.NewSymbol (); def tb = this.CoreEnv.Define (<[ decl: public class $(cname : name) { } ]>); mutable body = null; /*if (IsCompletionMode) { def lexer = LexerCompletion (this, Evaluator.code, Evaluator.code.Length); body = MainParser.ParseExpr (env, lexer); } else*/ { body = MainParser.ParseExpr (env, Evaluator.code, allow_empty = true); } match (body) { | null | <[ ]> => throw System.ApplicationException ("Nothing to parse.") | _ => () } def make_last (last) { | <[ def $_ = $_ ]> | <[ mutable $_ = $_ ]> | <[ def .. $_ ]> => <[ $last; $("stagedmacro" : usesite) (()) ]> | _ => <[ $("stagedmacro" : usesite) ($last) ]> } // If the code ends with an assignment, append `()'. // Put a call to stagedmacro at the end of the evaluated code // with the last expression moved to the stagedmacro argument. def whole = match (body) { | <[ {..$seq } ]> => def (beg, last) = List.DivideLast (seq); def last = make_last (last); <[ {..$ (beg + [last]) } ]> | _ => make_last (body) } // Recreate variables defined in previous calls to Eval. def inits = List.FoldLeft (Evaluator.locals, [], fun (x, acc) { def (mut, name, ty) = x; match ((mut, ty)) { | (false, <[ System.Object ]>) => <[ def $(name : usesite) = List.Hd (Evaluator.vals) : $ty ]> :: <[ Evaluator.vals = List.Tl (Evaluator.vals) ]> :: acc | (false, _) => <[ def $(name : usesite) = List.Hd (Evaluator.vals) :> $ty ]> :: <[ Evaluator.vals = List.Tl (Evaluator.vals) ]> :: acc | (_, <[ System.Object ]>) => <[ mutable $(name : usesite) = List.Hd (Evaluator.vals) : $ty ]> :: <[ Evaluator.vals = List.Tl (Evaluator.vals) ]> :: acc; | _ => <[ mutable $(name : usesite) = List.Hd (Evaluator.vals) :> $ty ]> :: <[ Evaluator.vals = List.Tl (Evaluator.vals) ]> :: acc; } }); def w = <[ {.. $(inits + [whole]) } ]>; // PrettyPrint.PrintExpr (None (), w); tb.Define (<[ decl: public static Run () : list [System.Object] { $w } ]>); tb.Compile (); MacroColors.PopColor (); } internal class StagedMacro : IMacro { public Run (ctx : Typer, val : list [SyntaxElement]) : PExpr { match (val) { // Fish out variables/functions and store them in our fields. | [SyntaxElement.Expression (expr)] => Evaluator.locals = []; def l = ctx.LocalContext.GetLocals (); mutable values = l.Fold ([], fun (n : Name, loc : LocalValue, acc) { match (n.ToString ()) { | "it" => acc | _ when loc.ValKind is LocalValue.Kind.BlockReturn => acc | _ => Evaluator.locals = (loc.IsMutable, loc.Name, PrettyPrint.TyVarToParseTree (loc.Type)) :: Evaluator.locals; <[ ($(n : name) : System.Object) ]> :: acc } }); def texpr = ctx.TypeExpr (expr); match (texpr.MType()) { | MType.Void => <[ $(texpr : typed); [..$values] ]> | _ => Evaluator.locals = (false, "it", PrettyPrint.TyVarToParseTree (texpr.MType())) :: Evaluator.locals; values ::= <[ $expr : System.Object ]>; <[ [..$values] ]> } | _ => Util.ice () } } // This below is only to satisfy the interface requirements. public CallTransform (l : list [PExpr]) : list [SyntaxElement] { List.Map (l, fun (x) { SyntaxElement.Expression (x) }) } public GetName () : string { "stagedmacro" } public GetNamespace () : string { "" } public Location : Location { get { Location.Default } } public IsInherited : bool { get { false } } public IsTailRecursionTransparent : bool { get { false } } public Keywords : list [string] { get { [] } } public SyntaxExtension () : GrammarElement * (list [SyntaxElement] -> list [SyntaxElement]) { (null, fun (_) { [] }) } public Usage : Nemerle.MacroUsageAttribute { get { null } } } } }