I want to use the Roslyn Scripting Engine to provide a scripting engine for
our Software. Our software exposes some of it's api as singletons. However i cannot access those statics in the executed code.
For example i want to do something like this in script:
IOManager.Instance.DoWork(...);
When i do this:
var scriptContent = "IOManager.Instance.DoWork(...);
var options = ScriptOptions.Default;
options.AddReference(this.GetType().Assembly);
var script = CSharpScript.Create(scriptContent, options);
await script.RunAsync();
I get this error:
The name 'IOManager' does not exist in the current context
I thought maybe adding a reference to the current assembly might fix this problem. But it doesn't. I also know, that it is possible to set a global object to the script context. But i want to expose all statics/singletons accessible where i execute the script, to the script itself.
Thank you for your help.
You need to add the import for IOManager in your script:
var script = CSharpScript.Create( "IOManager.Instance.DoWork(...)" , ScriptOptions.Default.AddImports( "Namespace for IOManager" ) );
Related
My Goal
My team decided, that they wanted to incorporate automation to enforce coding guidelines in .razor files.
Since we're already using StyleCopAnalyzers, I might implement our own Analyzer to achieve this goal.
Example
To better make you understand what's in my mind, consider the following example.
Let the following be valid code:
<MyGrid>
<MyItem></MyItem>
<MyItem></MyItem>
<MyItem></MyItem>
</MyGrid>
Now, If someone wouldn't encapsulate <MyItem> within <MyGrid> it would cause the Analyzer to report a diagnostic like "MyItem should be direct child of a MyGrid".
What I tried
Firstly, I followed microsoft's tutorial: Write your first analyzer and code fix. This worked perfectly well.
However, my next step was to create a Sample Blazor WASM Application using the default template. When I executed the MakeConstAnalyzer on the Sample Blazor Application, I'd get the following behavior.
Modifying Program.cs like this:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
// I get squiggly lines here with the expected analyzer message from the MakeConstAnalyzer
int bla = 0;
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}
}
But modifying Counter.razor like this:
private void IncrementCount()
{
int bla = 0; // add this line
currentCount++;
}
now, this won't show the analyzers message. Of course this will
trigger a compiler warning, but mainly this tells me, that for some
reason the Analyzer isn't executed on the Counter.razor file.
The Microsoft.CodeAnalysis.Razor Package
I thought, I'd mention it here. I found it on Nuget and tried to integrate it in the sample.
Caution
I have no clue, If the purpose of the package is even remotely connected, to what I want to achieve, since I really didn't find any documentation.
Since the microsoft tutorial clearly states, that any Analyzer must in some way inherit DiagnosticAnalyzer, I also checked the package for an implementation. But in both, the Microsoft.CodeAnalysis.Razor and Microsoft.AspNetCore.Razor.Language Package there is no class, that inherits from DiagnosticAnalyzer.
I've also tried to decorate my Analyzer, to specify, that it should also parse *.razor files, like that:
[DiagnosticAnalyzer(LanguageNames.CSharp, Microsoft.CodeAnalysis.Razor.RazorLanguage.Name)]
Summary
At this point, I don't know, what else to try. Also I didn't find anything remotely like that in the web.
These are my Questions:
How can I instruct my Analyzer to also parse .razor files?
Also how would I access the razor code in that approach?
I can imagine to either use Analyzers Syntax to traverse the Component Tree, but I think I could realize most of my usecases by only having the plain unparsed code.
I'm actually integrating the amazing RoslynPad into a WinForms application and working damn well.
The point of the integration is allowing the user to type in some C# code so it can be used in a future.
Thing is I'm interested on "capping" the user so he could just use some System or even LinQ functions. I don't want to allow the user to think he is allowed to use System.IO and others. Of course I can't prevent him/her typing System.IO.File.Delete, but will surely help if the System.IO's Assembly is not loaded into the RoslynPad's IntelliSense.
The source code typed by the user is going to be compiled locally before being saved into the DB. I'm adding just a few and necessary Assemblies for the compilation, so if System.IO it won't compile, of course.
As I explained, I just want to cap the Intellisense, so they don't think they have access to almost the whole .NET Framework.
EDIT: Added the actual implementation actually done. I'm loading "RoslynPad.Roslyn.Windows" and "RoslynPad.Editor.Windows" assemblies to the editor.
private RoslynCodeEditor _editor;
private void InitializeEditor(string sourceCode)
{
if (string.IsNullOrWhiteSpace(sourceCode))
sourceCode = string.Empty;
_editor = new RoslynCodeEditor();
var workingDirectory = Directory.GetCurrentDirectory();
var roslynHost = new RoslynHost(additionalAssemblies: new[]
{
Assembly.Load("RoslynPad.Roslyn.Windows"),
Assembly.Load("RoslynPad.Editor.Windows")
});
_editor.Initialize(roslynHost, new ClassificationHighlightColors(), workingDirectory, sourceCode);
_editor.FontFamily = new System.Windows.Media.FontFamily("Consolas");
_editor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
_editor.FontSize = 12.75f;
elementHost1.Child = _editor;
this.Controls.Add(elementHost1);
}
You can use pass a RoslynHostReferences instance to the RoslynHost constructor, and decide which assemblies and namespaces are imported by default.
You could use the same logic as Default, just remove System.IO.Path from the type list.
Note that System.IO is not an assembly, but rather a namespace, which is in the core library, so there's no simple way to completely remove it.
I figured out I cannot load one script library from another easily:
module.csx
string SomeFunction() {
return "something";
}
script.csx
ExecuteFile("module.csx");
SomeFunction() <-- causes compile error "SomeFunction" does not exist
This is because the compiler does not know of module.csx at the time it compiles script.csx afaiu. I can add another script to load the two files from that one, and that will work. However thats not that pretty.
Instead I like to make my scripthost check for a special syntax "load module" within my scripts, and execute those modules before actual script execution.
script.csx
// load "module.csx"
SomeFunction()
Now, with some basic string handling, I can figure out which modules to load (lines that contains // load ...) and load that files (gist here https://gist.github.com/4147064):
foreach(var module in scriptModules) {
session.ExecuteFile(module);
}
return session.Execute(script)
But - since we're talking Roslyn, there should be some nice way to parse the script for the syntax I'm looking for, right?
And it might even exist a way to handle module libraries of code?
Currently in Roslyn there is no way to reference another script file. We are considering moving #load from being a host command of the Interactive Window to being a part of the language (like #r), but it isn't currently implemented.
As to how to deal with the strings, you could parse it normally, and then look for pre-processor directives that are of an unknown type and delve into the structure that way.
Support for #load in script files has been added as of https://github.com/dotnet/roslyn/commit/f1702c.
This functionality will be available in Visual Studio 2015 Update 1.
Include the script:
#load "common.csx"
...
And configure the source resolver when you run the scripts:
Script<object> script = CSharpScript.Create(code, ...);
var options = ScriptOptions.Default.WithSourceResolver(new SourceFileResolver(new string[] { }, baseDirectory));
var func = script.WithOptions(options).CreateDelegate()
...
IronRuby and VS2010 noob question:
I'm trying to do a spike to test the feasibility of interop between a C# project and an existing RubyGem rather than re-invent that particular wheel in .net. I've downloaded and installed IronRuby and the RubyGems package, as well as the gem I'd ultimately like to use.
Running .rb files or working in the iirb Ruby console is without problems. I can load the both the RubyGems package, and the gem itself and use it, so, at least for that use case, my environment is set up correctly.
However, when I try to do the same sort of thing from within a C# (4.0) console app, it complains about the very first line:
require 'RubyGems'
With the error:
no such file to load -- rubygems
My Console app looks like this:
using System;
using IronRuby;
namespace RubyInteropSpike
{
class Program
{
static void Main(string[] args)
{
var runtime = Ruby.CreateRuntime();
var scope = runtime.ExecuteFile("test.rb");
Console.ReadKey();
}
}
}
Removing the dependencies and just doing some basic self-contained Ruby stuff works fine, but including any kind of 'requires' statement seems to cause it to fail.
I'm hoping that I just need to pass some additional information (paths, etc) to the ruby runtime when I create it, and really hoping that this isn't some kind of limitation, because that would make me sad.
Short answer: Yes, this will work how you want it to.You need to use the engine's SetSearchPaths method to do what you wish.
A more complete example
(Assumes you loaded your IronRuby to C:\IronRubyRC2 as the root install dir)
var engine = IronRuby.Ruby.CreateEngine();
engine.SetSearchPaths(new[] {
#"C:\IronRubyRC2\Lib\ironruby",
#"C:\IronRubyRC2\Lib\ruby\1.8",
#"C:\IronRubyRC2\Lib\ruby\site_ruby\1.8"
});
engine.Execute("require 'rubygems'"); // without SetSearchPaths, you get a LoadError
/*
engine.Execute("require 'restclient'"); // install through igem, then check with igem list
engine.Execute("puts RestClient.get('http://localhost/').body");
*/
Console.ReadKey();
Thanks to suggestions from a previous question, I'm busy trying out IronPython, IronRuby and Boo to create a DSL for my C# app. Step one is IronPython, due to the larger user and knowledge base. If I can get something to work well here, I can just stop.
Here is my problem:
I want my IronPython script to have access to the functions in a class called Lib. Right now I can add the assembly to the IronPython runtime and import the class by executing the statement in the scope I created:
// load 'ScriptLib' assembly
Assembly libraryAssembly = Assembly.LoadFile(libraryPath);
_runtime.LoadAssembly(libraryAssembly);
// import 'Lib' class from 'ScriptLib'
ScriptSource imports = _engine.CreateScriptSourceFromString("from ScriptLib import Lib", SourceCodeKind.Statements);
imports.Execute(_scope);
// run .py script:
ScriptSource script = _engine.CreateScriptSourceFromFile(scriptPath);
script.Execute(_scope);
If I want to run Lib::PrintHello, which is just a hello world style statement, my Python script contains:
Lib.PrintHello()
or (if it's not static):
library = new Lib()
library.PrintHello()
How can I change my environment so that I can just have basic statments in the Python script like this:
PrintHello
TurnOnPower
VerifyFrequency
TurnOffPower
etc...
I want these scripts to be simple for a non-programmer to write. I don't want them to have to know what a class is or how it works. IronPython is really just there so that some basic operations like for, do, if, and a basic function definition don't require my writing a compiler for my DSL.
You should be able to do something like:
var objOps = _engine.Operations;
var lib = new Lib();
foreach (string memberName in objOps.GetMemberNames(lib)) {
_scope.SetVariable(memberName, objOps.GetMember(lib, memberName));
}
This will get all of the members from the lib objec and then inject them into the ScriptScope. This is done w/ the Python ObjectOperations class so that the members you get off will be Python members. So if you then do something similar w/ IronRuby the same code should basically work.