Last active
March 26, 2020 02:50
-
-
Save RGBKnights/cc962eaab267eb773bda28450d1b0255 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Azure.WebJobs; | |
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.CSharp.Scripting; | |
using Microsoft.CodeAnalysis.Scripting; | |
using Microsoft.Extensions.Logging; | |
using System; | |
using System.Linq; | |
using System.Collections.Generic; | |
using System.Collections.Immutable; | |
using System.IO; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Microsoft.CodeAnalysis.CSharp; | |
using Microsoft.CodeAnalysis.CSharp.Syntax; | |
using Microsoft.CodeAnalysis.Text; | |
namespace FnScriptEngine.Test | |
{ | |
public class Globals | |
{ | |
public GConstants Constants { get; set; } = new GConstants(); | |
public GMemory Memory { get; set; } = new GMemory(); | |
public GMap Map { get; set; } = new GMap(); | |
public GPlayer Player { get; set; } = new GPlayer(); | |
public class GConstants | |
{ | |
//COLOR_RED: 1, | |
} | |
public class GMemory | |
{ | |
} | |
public class GMap | |
{ | |
} | |
public class GPlayer | |
{ | |
internal int Counter { get => Commands.Count; } | |
internal List<GCommand> Commands { get; set; } = new List<GCommand>(); | |
public void AddCommand(GCommand cmd) | |
{ | |
Commands.Add(cmd); | |
} | |
} | |
public class GCommand | |
{ | |
} | |
} | |
public static class Function1 | |
{ | |
private static List<Type> _allowedTypes = new List<Type>() { | |
typeof(object), | |
typeof(string), | |
typeof(int), | |
typeof(short), | |
typeof(long), | |
typeof(uint), | |
typeof(ushort), | |
typeof(ulong), | |
typeof(double), | |
typeof(float), | |
typeof(bool), | |
typeof(char), | |
typeof(byte), | |
typeof(sbyte), | |
typeof(decimal), | |
typeof(System.NullReferenceException), | |
typeof(System.ArgumentException), | |
typeof(System.ArgumentNullException), | |
typeof(System.InvalidOperationException), | |
typeof(System.Exception), | |
typeof(System.DivideByZeroException), | |
typeof(System.InvalidCastException), | |
typeof(System.NotSupportedException), | |
typeof(System.Nullable<>), | |
typeof(System.StringComparer), | |
typeof(System.IEquatable<>), | |
typeof(System.IComparable), | |
typeof(System.IComparable<>), | |
typeof(System.Random), | |
typeof(System.Math), | |
typeof(System.Enum), | |
typeof(System.IDisposable), | |
}; | |
[FunctionName("Function1")] | |
public static async Task Run( | |
[QueueTrigger("cosmos-match-trigger")]string code, | |
ILogger log | |
) | |
{ | |
try | |
{ | |
var globals = new Globals(); | |
await RunScriptAsync(globals, code); | |
log.LogInformation($"Map.Counter: {globals.Player.Counter}"); | |
log.LogInformation($"Player.Commands: {globals.Player.Commands.Count}"); | |
} | |
catch(InvalidOperationException e) | |
{ | |
log.LogError(e.Message); | |
} | |
catch (CompilationErrorException e) | |
{ | |
log.LogError(string.Join(Environment.NewLine, e.Diagnostics)); | |
} | |
} | |
private static async Task RunScriptAsync(Globals globals, string code) | |
{ | |
var options = ScriptOptions.Default | |
.WithReferences(ImmutableArray<MetadataReference>.Empty) | |
.WithImports(ImmutableArray<string>.Empty); | |
var script = CSharpScript.Create(code, options, globals.GetType()); | |
var diagnostics = script.Compile(); | |
var compilation = script.GetCompilation(); | |
ScriptAnalyzer(compilation); | |
await script.RunAsync(globals); | |
} | |
private static void ScriptAnalyzer(Compilation compilation) | |
{ | |
var actualTree = compilation.SyntaxTrees.First(); | |
var model = compilation.GetSemanticModel(compilation.SyntaxTrees.First()); | |
var root = (CompilationUnitSyntax)actualTree.GetRoot(); | |
var invocationExpressions = root.DescendantNodes().Where(i => i.IsKind(SyntaxKind.InvocationExpression)).OfType<InvocationExpressionSyntax>(); | |
var allowedClassesCalls = _allowedTypes.Select(i => i.FullName).ToHashSet(); | |
foreach (var invocationExpression in invocationExpressions) | |
{ | |
var memberAccessExpressionSyntax = invocationExpression.Expression as MemberAccessExpressionSyntax; | |
var symbolInfo = model.GetSymbolInfo(memberAccessExpressionSyntax); | |
if (symbolInfo.Symbol != null) | |
{ | |
if (!allowedClassesCalls.Contains(symbolInfo.Symbol.ContainingSymbol.ToString())) | |
{ | |
var location = invocationExpression.GetLocation(); | |
throw new InvalidOperationException(GetFormatedLocationError(location)); | |
} | |
} | |
else if (symbolInfo.CandidateSymbols != null) | |
{ | |
// if any ambiguity in the method, candidates are here. Surprisingly, for the actual execution roslyn has no pb choosing the right one | |
foreach (var symbol in symbolInfo.CandidateSymbols) | |
{ | |
if (!allowedClassesCalls.Contains(symbol.ContainingSymbol.ToString())) | |
{ | |
var location = invocationExpression.GetLocation(); | |
throw new InvalidOperationException(GetFormatedLocationError(location)); | |
} | |
} | |
} | |
} | |
} | |
private static string GetFormatedLocationError(Location location) | |
{ | |
var position = location.GetLineSpan(); | |
var code = location.SourceTree.GetText().GetSubText(location.SourceSpan); | |
var msg = $"({position.StartLinePosition.Line + 1},{position.StartLinePosition.Character + 1}): error VS00001: \"{code}\" (invalid invocation)"; | |
return msg; | |
} | |
private static void RedirectConsoleIO() | |
{ | |
// TODO: LOG...? | |
var stdOut = new StringBuilder(); | |
Console.SetIn(new StringReader(string.Empty)); | |
Console.SetOut(new StringWriter(stdOut)); | |
Console.SetError(new StringWriter()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment