Skip to content

Commit e8613e9

Browse files
Egor MedvedevEgor Medvedev
authored andcommitted
Add mono evaluator runner
1 parent 9969791 commit e8613e9

File tree

13 files changed

+501
-5
lines changed

13 files changed

+501
-5
lines changed

EmbeddedScripts.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedScripts.Python.Pyth
5151
EndProject
5252
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedScripts.Python.PythonNet.Tests", "Tests\EmbeddedScripts.Python.PythonNet.Tests\EmbeddedScripts.Python.PythonNet.Tests.csproj", "{02D102CC-1058-431E-BEFB-BEF71E8ACE6C}"
5353
EndProject
54+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedScripts.CSharp.MonoEvaluator", "Src\EmbeddedScripts.CSharp.MonoEvaluator\EmbeddedScripts.CSharp.MonoEvaluator.csproj", "{FD6B10EC-5E5A-4EB5-8AF8-45C1DF21C9CC}"
55+
EndProject
56+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedScripts.CSharp.MonoEvaluator.Tests", "Tests\EmbeddedScripts.CSharp.MonoEvaluator.Tests\EmbeddedScripts.CSharp.MonoEvaluator.Tests.csproj", "{904B2A59-AC9B-4811-AAF0-43EBC9E73E1C}"
57+
EndProject
5458
Global
5559
GlobalSection(SharedMSBuildProjectFiles) = preSolution
5660
Src\EmbeddedScripts.CSharp.Shared\EmbeddedScripts.CSharp.Shared.projitems*{2e1ca5c1-3fbf-4bec-9db2-8e78b3bc8169}*SharedItemsImports = 13
@@ -143,6 +147,14 @@ Global
143147
{02D102CC-1058-431E-BEFB-BEF71E8ACE6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
144148
{02D102CC-1058-431E-BEFB-BEF71E8ACE6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
145149
{02D102CC-1058-431E-BEFB-BEF71E8ACE6C}.Release|Any CPU.Build.0 = Release|Any CPU
150+
{FD6B10EC-5E5A-4EB5-8AF8-45C1DF21C9CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
151+
{FD6B10EC-5E5A-4EB5-8AF8-45C1DF21C9CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
152+
{FD6B10EC-5E5A-4EB5-8AF8-45C1DF21C9CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
153+
{FD6B10EC-5E5A-4EB5-8AF8-45C1DF21C9CC}.Release|Any CPU.Build.0 = Release|Any CPU
154+
{904B2A59-AC9B-4811-AAF0-43EBC9E73E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
155+
{904B2A59-AC9B-4811-AAF0-43EBC9E73E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
156+
{904B2A59-AC9B-4811-AAF0-43EBC9E73E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
157+
{904B2A59-AC9B-4811-AAF0-43EBC9E73E1C}.Release|Any CPU.Build.0 = Release|Any CPU
146158
EndGlobalSection
147159
GlobalSection(SolutionProperties) = preSolution
148160
HideSolutionNode = FALSE
@@ -166,6 +178,8 @@ Global
166178
{AAFED3B1-E319-45BB-B213-653DC4F6819F} = {DB199BCF-967A-46EE-89E5-BBC0B6D59BE5}
167179
{B1F9D200-DE38-4A05-B573-07264709DDFB} = {8EE291DB-ACE3-40EE-8FD9-1A2C918DD5F2}
168180
{02D102CC-1058-431E-BEFB-BEF71E8ACE6C} = {DB199BCF-967A-46EE-89E5-BBC0B6D59BE5}
181+
{FD6B10EC-5E5A-4EB5-8AF8-45C1DF21C9CC} = {DF1BF043-0808-4B9A-92E4-1C53A05056E9}
182+
{904B2A59-AC9B-4811-AAF0-43EBC9E73E1C} = {DB199BCF-967A-46EE-89E5-BBC0B6D59BE5}
169183
EndGlobalSection
170184
GlobalSection(ExtensibilityGlobals) = postSolution
171185
SolutionGuid = {9C12123A-C2E9-46DF-AC21-40D0640056AE}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using EmbeddedScripts.CSharp.Shared.CodeGeneration;
3+
4+
namespace EmbeddedScripts.CSharp.MonoEvaluator.CodeGeneration
5+
{
6+
public class MonoSpecificCodeGenerator
7+
{
8+
public string GenerateResolveLine(string instanceAlias, Type instanceType, int runnerId)
9+
{
10+
var generatedTypeName = new ResolvingCodeGenerator().GenerateTypeName(instanceType);
11+
return $"var {instanceAlias} = ({generatedTypeName})HostFields.Get({runnerId})[\"{instanceAlias}\"]";
12+
}
13+
14+
public string GenerateUsingLine(Type type) =>
15+
$"using {type.Namespace};";
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net461</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\EmbeddedScripts.Shared\EmbeddedScripts.Shared.csproj" />
9+
</ItemGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Mono.CSharp" Version="4.0.0.143" />
13+
</ItemGroup>
14+
15+
<Import Project="..\EmbeddedScripts.CSharp.Shared\EmbeddedScripts.CSharp.Shared.projitems" Label="Shared" />
16+
17+
</Project>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Linq;
2+
using System.Reflection;
3+
using Mono.CSharp;
4+
5+
namespace EmbeddedScripts.CSharp.MonoEvaluator
6+
{
7+
internal static class EvaluatorExtension
8+
{
9+
internal static bool TryReferenceAssembly(this Evaluator evaluator, Assembly assembly)
10+
{
11+
var importer = (ReflectionImporter) evaluator.GetType()
12+
.GetField("importer", BindingFlags.Instance | BindingFlags.NonPublic)?
13+
.GetValue(evaluator);
14+
15+
var referencedAssembliesNames = importer?.Assemblies.Select(a => a.FullName);
16+
var containsAssembly = referencedAssembliesNames?.Contains(assembly.FullName) ?? false;
17+
18+
if (containsAssembly)
19+
return false;
20+
21+
evaluator.ReferenceAssembly(assembly);
22+
23+
return true;
24+
}
25+
}
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Collections.Concurrent;
2+
using System.Collections.Generic;
3+
4+
namespace EmbeddedScripts.CSharp.MonoEvaluator.Hosting
5+
{
6+
public static class HostFields
7+
{
8+
private static readonly ConcurrentDictionary<int, Dictionary<string, object>> Fields =
9+
new ConcurrentDictionary<int, Dictionary<string, object>>();
10+
11+
internal static void CreateNewEntry(int id)
12+
{
13+
if (!Fields.ContainsKey(id))
14+
Fields.TryAdd(id, new Dictionary<string, object>());
15+
}
16+
17+
public static Dictionary<string, object> Get(int id) =>
18+
Fields[id];
19+
}
20+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Data;
3+
using System.IO;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using EmbeddedScripts.CSharp.MonoEvaluator.CodeGeneration;
7+
using EmbeddedScripts.CSharp.MonoEvaluator.Hosting;
8+
using EmbeddedScripts.Shared;
9+
using EmbeddedScripts.Shared.Exceptions;
10+
using Mono.CSharp;
11+
12+
namespace EmbeddedScripts.CSharp.MonoEvaluator
13+
{
14+
public class MonoCSharpRunner : ICodeRunner, IEvaluator
15+
{
16+
private readonly StringBuilder _errorLogBuilder = new StringBuilder();
17+
private readonly Evaluator _evaluator;
18+
private readonly MonoSpecificCodeGenerator _codeGenerator = new MonoSpecificCodeGenerator();
19+
private readonly int _runnerId = new Random().Next(0, int.MaxValue);
20+
21+
/// <summary>
22+
/// Evaluator doesn't throw any syntax exceptions. Instead, it just logged errors into StreamReportPrinter. So we using
23+
/// StringWriter and after each run check if any error is logged or not. If error is logged method throws an exception
24+
/// with logged message.
25+
/// </summary>
26+
/// <exception cref="ScriptSyntaxErrorException"></exception>
27+
private void ThrowIfErrorLogged()
28+
{
29+
var message = _errorLogBuilder.ToString();
30+
31+
if (string.IsNullOrEmpty(message))
32+
return;
33+
34+
_errorLogBuilder.Clear();
35+
36+
throw new ScriptSyntaxErrorException(message);
37+
}
38+
39+
public MonoCSharpRunner()
40+
{
41+
var defaultSettings = new CompilerSettings();
42+
_evaluator = new Evaluator(new CompilerContext(
43+
defaultSettings, new StreamReportPrinter(new StringWriter(_errorLogBuilder))));
44+
45+
HostFields.CreateNewEntry(_runnerId);
46+
var hostFieldsType = typeof(HostFields);
47+
_evaluator.ReferenceAssembly(hostFieldsType.Assembly);
48+
49+
RunAsync(_codeGenerator.GenerateUsingLine(hostFieldsType));
50+
}
51+
52+
public Task RunAsync(string code)
53+
{
54+
_evaluator.Run(code);
55+
56+
ThrowIfErrorLogged();
57+
58+
return Task.CompletedTask;
59+
}
60+
61+
public ICodeRunner Register<T>(T instance, string alias)
62+
{
63+
var instanceType = typeof(T);
64+
HostFields.Get(_runnerId).Add(alias, instance);
65+
66+
_evaluator.TryReferenceAssembly(instanceType.Assembly);
67+
var resolveLine =_codeGenerator.GenerateResolveLine(alias, instanceType, _runnerId);
68+
RunAsync(resolveLine);
69+
70+
return this;
71+
}
72+
73+
public Task<T> EvaluateAsync<T>(string expression)
74+
{
75+
var evaluationComplete = _evaluator.Evaluate(expression, out var result, out var resultSet);
76+
77+
ThrowIfErrorLogged();
78+
79+
if (evaluationComplete != null)
80+
throw new ScriptSyntaxErrorException("Given expression is partial");
81+
if (!resultSet)
82+
throw new ScriptEngineErrorException("Given expression is statement");
83+
84+
return Task.FromResult((T)result);
85+
}
86+
}
87+
}

Src/EmbeddedScripts.CSharp.Shared/CodeGeneration/ResolvingCodeGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ private string GenerateCode(Container container, Func<string, Type, string> gene
2121
.Select(alias => generator(alias, container.GetTypeByAlias(alias)))
2222
.Aggregate("", (cur, nxt) => cur + nxt + Environment.NewLine);
2323

24-
private string GenerateTypeName(Type type)
24+
public string GenerateTypeName(Type type)
2525
{
2626
if (!type.IsGenericType)
2727
return type.FullName;

Src/EmbeddedScripts.Shared/Container.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void Clear()
3434
public IEnumerable<string> VariableAliases => Types.Keys;
3535
public Type GetTypeByAlias(string alias) => Types[alias];
3636

37-
private Dictionary<string, Type> Types { get; } = new();
38-
private Dictionary<string, object> Instances { get; } = new();
37+
private Dictionary<string, Type> Types { get; } = new Dictionary<string, Type>();
38+
private Dictionary<string, object> Instances { get; } = new Dictionary<string, object>();
3939
}
4040
}

Src/EmbeddedScripts.Shared/EmbeddedScripts.Shared.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5-
<LangVersion>latest</LangVersion>
65
</PropertyGroup>
76

87
</Project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net461</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="16.6.1" Condition="$(TargetFramework.StartsWith('net4')) AND '$(OS)' == 'Unix'" />
10+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
11+
<PackageReference Include="xunit" Version="2.4.1" />
12+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
13+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
14+
<PrivateAssets>all</PrivateAssets>
15+
</PackageReference>
16+
<PackageReference Include="coverlet.collector" Version="3.0.2">
17+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18+
<PrivateAssets>all</PrivateAssets>
19+
</PackageReference>
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<ProjectReference Include="..\..\Src\EmbeddedScripts.CSharp.MonoEvaluator\EmbeddedScripts.CSharp.MonoEvaluator.csproj" />
24+
<ProjectReference Include="..\..\Src\EmbeddedScripts.Shared\EmbeddedScripts.Shared.csproj" />
25+
</ItemGroup>
26+
27+
</Project>

0 commit comments

Comments
 (0)