using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Antlr.Runtime.Tree;
using JetBrains.Annotations;
using Kaleida.ServiceMonitor.Model.Runtime;
using Kaleida.ServiceMonitor.Model.StructuredSource;
namespace Kaleida.ServiceMonitor.Model.Parsing
{
public static class ScriptParser
{
[CanBeNull]
private static ScriptCompiler compiler;
[CanBeNull]
private static ReflectionOperationFactory commandFactory;
public static void Initialise(string searchDirectoryPath)
{
if(!Directory.Exists(searchDirectoryPath))
throw new InvalidOperationException(string.Format("The required directory '{0}' does not exist", searchDirectoryPath));
var dllPaths = Directory.GetFiles(searchDirectoryPath, "*.dll");
var assemblies = dllPaths.Select(LoadFile);
commandFactory = new ReflectionOperationFactory(assemblies);
Initialise(commandFactory, commandFactory);
}
internal static void Initialise(IPreparedRequestFactory preparedRequestFactory, IResponseHandlerFactory responseHandlerFactory)
{
compiler = new ScriptCompiler(preparedRequestFactory, responseHandlerFactory);
}
public static RuntimeEnvironment GetRuntimeEnvironment()
{
if(commandFactory == null) throw new InvalidOperationException("Must first call ScriptParser.Initialise");
return commandFactory.GetRuntimeEnvironment();
}
private static Assembly LoadFile(string path)
{
try
{
return Assembly.LoadFile(path);
}
catch (NotSupportedException)
{
throw new InvalidOperationException(string.Format("Could not load '{0}'. Please ensure that all DLL files in this directory are unblocked. In Windows Explorer right-mouse on the file, click 'Properties', then the 'Unblock' button", path));
}
}
public static ScriptDefinition Parse(this string scriptSource)
{
try
{
var tree = scriptSource.GetSyntaxTree();
return BuildScript(tree);
}
catch (Exception e)
{
throw new InvalidOperationException(string.Format("An error occurred whilst parsing '{0}': {1}", scriptSource, e.Message));
}
}
public static CompiledScript ParseAndBuild(this string text)
{
if (compiler == null) throw new InvalidOperationException("Must first call ScriptParser.Initialise");
var scriptDefinition = text.Parse();
var executableScript = compiler.Build(scriptDefinition);
return executableScript;
}
private static ScriptDefinition BuildScript(ITree tree)
{
var nodes = tree.GetChildren();
var directives = nodes.Where(i => i.Type == ScriptGrammarLexer.DIRECTIVE).Select(BuildDirective).ToList();
var lines = nodes.Where(i => i.Type == ScriptGrammarLexer.COMMAND).Select(BuildCommandWithHandler).ToList();
var comments = nodes.Where(i => i.Type == ScriptGrammarLexer.COMMENT).Select(BuildComment).ToList();
return new ScriptDefinition(directives, lines, comments);
}
private static DirectiveDefinition BuildDirective(ITree node)
{
ValidateNodeType(node, ScriptGrammarLexer.DIRECTIVE, "Directive");
if (node.ChildCount != 1)
throw new InvalidOperationException(string.Format("Expected directive node on line {0} to have one child but found {1}", node.Line, node.ChildCount));
var directiveNode = node.GetChild(0);
ValidateNodeType(directiveNode, ScriptGrammarLexer.PrefixedDirective, "Prefixed Directive");
if (!directiveNode.Text.StartsWith("#"))
throw new InvalidOperationException(string.Format("Expected directive node '{0}' on line {1} to begin with #", directiveNode.Text, directiveNode.Line));
var text = directiveNode.Text.Substring(1);
var arguments = directiveNode.GetChildren().Select(BuildArgument).ToList();
return new DirectiveDefinition(text, arguments);
}
private static OperationDefinition BuildCommandWithHandler(ITree node)
{
var actions = node.GetChildren().Select(BuildActionDefinition).ToList();
if(actions.Count < 1 || actions.Count > 2)
throw new InvalidOperationException(string.Format("Expected command line {0} to have 1 or 2 actions but found {1}", node.Line, actions.Count));
var requestAction = actions.First();
var responseAction = actions.Count == 2 ? actions.Last() : ActionDefinition.DoNothing;
return new OperationDefinition(requestAction, responseAction);
}
private static string BuildComment(ITree node)
{
ValidateNodeType(node, ScriptGrammarLexer.COMMENT, "Comment");
if (node.ChildCount != 1)
throw new InvalidOperationException(string.Format("Expected comment node on line {0} to have one child but found {1}", node.Line, node.ChildCount));
if (!node.GetChild(0).Text.StartsWith("//"))
throw new InvalidOperationException(string.Format("Expected comment on line {0} to start with //", node.Line));
return node.GetChild(0).Text.Substring("//".Length);
}
private static ActionDefinition BuildActionDefinition(ITree node)
{
ValidateNodeType(node, ScriptGrammarLexer.Identifier, "Identifier");
return new ActionDefinition(node.Text, node.GetChildren().Select(BuildArgument).ToList());
}
private static string BuildArgument(ITree node)
{
ValidateNodeType(node, ScriptGrammarLexer.QuotedString, "String");
return node.Text.Substring(1, node.Text.Length - 2).Replace("\"\"", "\"");
}
private static void ValidateNodeType(ITree node, int expectedType, string expectedTypeName)
{
if (node.Type != expectedType)
throw new InvalidOperationException(string.Format("Expected '{0}' (at line {1}, column {2}) to be type {3} but it was {4}", node.Text, node.Line, node.CharPositionInLine, expectedTypeName, node.Type));
}
}
}