Skip to content

Instantly share code, notes, and snippets.

@RGBKnights
Last active March 26, 2020 02:50
Show Gist options
  • Save RGBKnights/cc962eaab267eb773bda28450d1b0255 to your computer and use it in GitHub Desktop.
Save RGBKnights/cc962eaab267eb773bda28450d1b0255 to your computer and use it in GitHub Desktop.
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