添加项目文件。
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
namespace TightWiki.Engine.Function.Exceptions
|
||||
{
|
||||
public class WikiFunctionPrototypeNotDefinedException : Exception
|
||||
{
|
||||
public WikiFunctionPrototypeNotDefinedException()
|
||||
{
|
||||
}
|
||||
|
||||
public WikiFunctionPrototypeNotDefinedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace TightWiki.Engine.Function.Exceptions
|
||||
{
|
||||
public class WikiFunctionPrototypeSyntaxError : Exception
|
||||
{
|
||||
public WikiFunctionPrototypeSyntaxError()
|
||||
{
|
||||
}
|
||||
|
||||
public WikiFunctionPrototypeSyntaxError(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
210
TightWiki.Engine.Function/FunctionCall.cs
Normal file
210
TightWiki.Engine.Function/FunctionCall.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about an actual function call, its supplied parameters, and is matched with a defined function.
|
||||
/// </summary>
|
||||
public class FunctionCall
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the function being called.
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
public FunctionPrototype Prototype { get; set; }
|
||||
/// <summary>
|
||||
/// The arguments supplied by the caller.
|
||||
/// </summary>
|
||||
public FunctionParameters Parameters { get; private set; }
|
||||
|
||||
public FunctionCall(FunctionPrototype prototype, List<string> args)
|
||||
{
|
||||
Prototype = prototype;
|
||||
Parameters = new FunctionParameters(this);
|
||||
Name = prototype.FunctionName;
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (arg.StartsWith(':') && arg.Contains('='))
|
||||
{
|
||||
var parsed = arg.Substring(1); //Skip the colon.
|
||||
int index = parsed.IndexOf('=');
|
||||
var name = parsed.Substring(0, index).Trim().ToLower();
|
||||
var value = parsed.Substring(index + 1).Trim();
|
||||
|
||||
Parameters.Named.Add(new NamedParameter(name, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
Parameters.Ordinals.Add(new OrdinalParameter(arg));
|
||||
}
|
||||
}
|
||||
|
||||
ApplyPrototype();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the passed value against the function prototype to ensure that the variable is the correct type, value, etc.
|
||||
/// </summary>
|
||||
/// <param name="segment"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private void EnforcePrototypeParamValue(PrototypeParameter param, string value)
|
||||
{
|
||||
if (param.Type == "bool")
|
||||
{
|
||||
if (bool.TryParse(value, out bool _) == false)
|
||||
{
|
||||
throw new Exception($"Function [{Name}], the value [{value}] passed to parameter [{param.Name}] could not be converted to boolean.");
|
||||
}
|
||||
}
|
||||
if (param.Type == "integer")
|
||||
{
|
||||
if (int.TryParse(value, out int _) == false)
|
||||
{
|
||||
throw new Exception($"Function [{Name}], the value [{value}] passed to parameter [{param.Name}] could not be converted to integer.");
|
||||
}
|
||||
}
|
||||
else if (param.Type == "float")
|
||||
{
|
||||
if (double.TryParse(value, out double _) == false)
|
||||
{
|
||||
throw new Exception($"Function [{Name}], the value [{value}] passed to parameter [{param.Name}] could not be converted to float.");
|
||||
}
|
||||
}
|
||||
|
||||
if (param.AllowedValues != null && param.AllowedValues.Count > 0)
|
||||
{
|
||||
if (param.AllowedValues.Contains(value.ToLower()) == false)
|
||||
{
|
||||
throw new Exception($"Function [{Name}], the value [{value}] passed to parameter [{param.Name}] is not allowed. Allowed values are [{string.Join(",", param.AllowedValues)}].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rolls through the supplied arguments and applies them to the prototype. Also identifies which supplied arguments are associated with each
|
||||
/// prototype argument and adds the ordinal based arguments to the name based collection. Ensures that each argument conforms with the prototype.
|
||||
/// </summary>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private void ApplyPrototype()
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
//Keep a list of the arguments as they are associated with the prototype so that we can later reference them by name.
|
||||
var namedToAddLater = new List<NamedParameter>();
|
||||
|
||||
//Handle non-infinite ordinal based required parameters:
|
||||
for (; index < Prototype.Parameters.Count; index++)
|
||||
{
|
||||
var param = Prototype.Parameters[index];
|
||||
|
||||
if (param.IsRequired == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (param.IsInfinite == true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (Parameters.Ordinals.Count > index)
|
||||
{
|
||||
//Good, we have a value.
|
||||
string value = Parameters.Ordinals[index].Value;
|
||||
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
|
||||
EnforcePrototypeParamValue(param, value.ToLower());
|
||||
|
||||
namedToAddLater.Add(new NamedParameter(param.Name, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Function [{Name}], the required parameter [{param.Name}] was not specified.");
|
||||
}
|
||||
}
|
||||
|
||||
bool hasEncounteredOptionalParameter = false;
|
||||
|
||||
//Handle remaining optional parameters:
|
||||
for (; index < Prototype.Parameters.Count; index++)
|
||||
{
|
||||
var param = Prototype.Parameters[index];
|
||||
|
||||
if (param.IsInfinite == true)
|
||||
{
|
||||
if (param.IsRequired == true)
|
||||
{
|
||||
//Make sure we have at least one of these required infinite parameters passed.
|
||||
if (Parameters.Ordinals.Count > index)
|
||||
{
|
||||
//Good, we have a value.
|
||||
string value = Parameters.Ordinals[index].Value;
|
||||
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
|
||||
EnforcePrototypeParamValue(param, value.ToLower());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Function [{Name}], the required infinite parameter [{param.Name}] was not passed.");
|
||||
}
|
||||
}
|
||||
|
||||
//Now that we have encountered an infinite parameter, it will swallow up all other ordinal based arguments. Might as well check the types and exit the loop.
|
||||
for (; index < Parameters.Ordinals.Count; index++)
|
||||
{
|
||||
string value = Parameters.Ordinals[index].Value;
|
||||
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
|
||||
EnforcePrototypeParamValue(param, value.ToLower());
|
||||
namedToAddLater.Add(new NamedParameter(param.Name, value));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (param.IsRequired == false)
|
||||
{
|
||||
hasEncounteredOptionalParameter = true;
|
||||
}
|
||||
|
||||
if (param.IsRequired == true && hasEncounteredOptionalParameter)
|
||||
{
|
||||
throw new Exception($"Function [{Name}], the required parameter [{param.Name}] was found after other optional parameters.");
|
||||
}
|
||||
else if (param.IsInfinite == true)
|
||||
{
|
||||
throw new Exception($"Function [{Name}], encountered an unexpected number of infinite parameters in prototype for [{param.Name}].");
|
||||
}
|
||||
|
||||
if (Parameters.Ordinals.Count > index)
|
||||
{
|
||||
string value = Parameters.Ordinals[index].Value;
|
||||
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
|
||||
EnforcePrototypeParamValue(param, value.ToLower());
|
||||
namedToAddLater.Add(new NamedParameter(param.Name, value));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var named in Parameters.Named)
|
||||
{
|
||||
var param = Prototype.Parameters.Where(o => o.Name.Equals(named.Name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault()
|
||||
?? throw new Exception($"Function [{Name}], the named parameter [{named.Name}] is not defined in the function prototype.");
|
||||
|
||||
EnforcePrototypeParamValue(param, named.Value);
|
||||
}
|
||||
|
||||
Parameters.Named.AddRange(namedToAddLater);
|
||||
|
||||
var unmatchedParams = Parameters.Ordinals.Where(o => o.IsMatched == false).ToList();
|
||||
if (unmatchedParams.Count != 0)
|
||||
{
|
||||
throw new Exception($"Function [{Name}], unmatched parameter value [{unmatchedParams.First().Value}].");
|
||||
}
|
||||
|
||||
var nonInfiniteParams = Prototype.Parameters.Where(o => o.IsInfinite == false).Select(o => o.Name.ToLower());
|
||||
var groups = Parameters.Named.Where(o => nonInfiniteParams.Contains(o.Name.ToLower())).GroupBy(o => o.Name.ToLower()).Where(o => o.Count() > 1);
|
||||
|
||||
if (groups.Any())
|
||||
{
|
||||
var group = groups.First();
|
||||
throw new Exception($"Function [{Name}], non-infinite parameter specified more than once: [{group.Key}].");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
TightWiki.Engine.Function/FunctionParameters.cs
Normal file
75
TightWiki.Engine.Function/FunctionParameters.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using NTDLS.Helpers;
|
||||
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public class FunctionParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// Variables set by ordinal.
|
||||
/// </summary>
|
||||
public List<OrdinalParameter> Ordinals { get; set; } = new();
|
||||
/// <summary>
|
||||
/// Variables set by name.
|
||||
/// </summary>
|
||||
public List<NamedParameter> Named { get; private set; } = new();
|
||||
|
||||
private readonly FunctionCall _owner;
|
||||
|
||||
public FunctionParameters(FunctionCall owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public T Get<T>(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = Named.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault()?.Value;
|
||||
if (value == null)
|
||||
{
|
||||
var prototype = _owner.Prototype.Parameters.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).First();
|
||||
return Converters.ConvertTo<T>(prototype.DefaultValue) ?? throw new Exception("Value cannot be null");
|
||||
}
|
||||
|
||||
return Converters.ConvertTo<T>(value) ?? throw new Exception("Value cannot be null");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Function [{_owner.Name}], {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public T Get<T>(string name, T defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = Named.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault()?.Value;
|
||||
if (value == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return Converters.ConvertTo<T>(value) ?? throw new Exception("Value cannot be null");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Function [{_owner.Name}], {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> GetList<T>(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
var values = Named.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))?
|
||||
.Select(o => Converters.ConvertTo<T>(o.Value) ?? throw new Exception("Value cannot be null"))?.ToList();
|
||||
|
||||
return values ?? new List<T>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Function [{_owner.Name}], {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
220
TightWiki.Engine.Function/FunctionParser.cs
Normal file
220
TightWiki.Engine.Function/FunctionParser.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using TightWiki.Engine.Function.Exceptions;
|
||||
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public static partial class FunctionParser
|
||||
{
|
||||
[GeneratedRegex(@"(##|{{|@@)([a-zA-Z_\s{][a-zA-Z0-9_\s{]*)\(((?<BR>\()|(?<-BR>\))|[^()]*)+\)")]
|
||||
private static partial Regex FunctionCallParser();
|
||||
|
||||
/// <summary>
|
||||
/// Parsed a function call, its parameters and matches it to a defined function and its prototype.
|
||||
/// </summary>
|
||||
/// <param name="functionCall"></param>
|
||||
/// <param name="parseEndIndex"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FunctionCall ParseAndGetFunctionCall(FunctionPrototypeCollection prototypes, string functionCall, out int parseEndIndex)
|
||||
{
|
||||
var rawArguments = new List<string>();
|
||||
|
||||
var parsed = ParseFunctionCall(prototypes, functionCall);
|
||||
|
||||
var prototype = prototypes.Get(parsed.Prefix, parsed.Name);
|
||||
if (prototype == null)
|
||||
{
|
||||
throw new WikiFunctionPrototypeNotDefinedException($"Function ({parsed.Name}) does not have a defined prototype.");
|
||||
}
|
||||
|
||||
parseEndIndex = parsed.EndIndex;
|
||||
|
||||
return new FunctionCall(prototype, parsed.RawArguments);
|
||||
}
|
||||
|
||||
public static ParsedFunctionCall ParseFunctionCall(FunctionPrototypeCollection prototypes, string functionCall)
|
||||
{
|
||||
string functionName = string.Empty;
|
||||
int parseEndIndex = 0;
|
||||
var rawArguments = new List<string>();
|
||||
|
||||
var firstLine = functionCall.Split('\n')?.FirstOrDefault();
|
||||
|
||||
if (firstLine == null || firstLine.Where(x => x == '(').Count() != firstLine.Where(x => x == ')').Count())
|
||||
{
|
||||
throw new WikiFunctionPrototypeSyntaxError($"Function parentheses mismatch.");
|
||||
}
|
||||
|
||||
string functionPrefix = functionCall.Substring(0, 2);
|
||||
|
||||
var parameterMatches = FunctionCallParser().Matches(firstLine);
|
||||
if (parameterMatches.Count > 0)
|
||||
{
|
||||
var match = parameterMatches[0];
|
||||
int paramStartIndex = match.Value.IndexOf('(');
|
||||
|
||||
functionName = match.Value[..paramStartIndex].ToLower().TrimStart(['{', '#', '@']).Trim();
|
||||
parseEndIndex = match.Index + match.Length;
|
||||
|
||||
string rawArgTrimmed = match.ToString().Substring(paramStartIndex, (match.ToString().Length - paramStartIndex));
|
||||
rawArguments = ParseRawArguments(rawArgTrimmed);
|
||||
}
|
||||
else //The function call has no parameters.
|
||||
{
|
||||
int endOfLine = functionCall.Substring(2).TakeWhile(c => char.IsLetterOrDigit(c)).Count(); //Find the first non-alphanumeric after the function identifier (##, @@, etc).
|
||||
functionName = functionCall.Substring(2, endOfLine).ToLower().TrimStart(['{', '#', '@']).Trim();
|
||||
parseEndIndex = endOfLine + 2;
|
||||
}
|
||||
|
||||
return new ParsedFunctionCall(functionPrefix, functionName, parseEndIndex, rawArguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses function parameters into a list of arguments based on comma separation.
|
||||
/// String do not need to be enclosed in double-quotes unless they contain commas.
|
||||
/// </summary>
|
||||
/// <param name="paramString"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static List<string> ParseRawArgumentsAddParenthesis(string paramString)
|
||||
{
|
||||
if (paramString.StartsWith('(') || paramString.EndsWith(')'))
|
||||
{
|
||||
throw new WikiFunctionPrototypeSyntaxError($"Unexpected '(' or ')'.");
|
||||
}
|
||||
|
||||
return ParseRawArguments($"({paramString})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses function parameters into a list of arguments based on comma separation.
|
||||
/// String do not need to be enclosed in double-quotes unless they contain commas.
|
||||
/// </summary>
|
||||
/// <param name="paramString"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static List<string> ParseRawArguments(string paramString)
|
||||
{
|
||||
List<string> ps = new();
|
||||
|
||||
int readPos = 0;
|
||||
|
||||
var singleParam = new StringBuilder();
|
||||
|
||||
if (paramString[readPos] != '(')
|
||||
{
|
||||
throw new WikiFunctionPrototypeSyntaxError($"Expected '('.");
|
||||
}
|
||||
|
||||
int parenNest = 1;
|
||||
|
||||
readPos++; //Skip the (
|
||||
|
||||
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (paramString[readPos] == '(')
|
||||
{
|
||||
parenNest++;
|
||||
}
|
||||
else if (paramString[readPos] == ')')
|
||||
{
|
||||
parenNest--;
|
||||
}
|
||||
|
||||
if (readPos == paramString.Length)
|
||||
{
|
||||
throw new WikiFunctionPrototypeSyntaxError($"Expected ')'.");
|
||||
}
|
||||
else if (paramString[readPos] == ')' && parenNest == 0)
|
||||
{
|
||||
readPos++; //Skip the )
|
||||
|
||||
if (parenNest == 0 && readPos != paramString.Length)
|
||||
{
|
||||
throw new WikiFunctionPrototypeSyntaxError($"Expected end of statement.");
|
||||
}
|
||||
|
||||
if (singleParam.Length > 0)
|
||||
{
|
||||
ps.Add(singleParam.ToString());
|
||||
}
|
||||
singleParam.Clear();
|
||||
|
||||
if (parenNest == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (paramString[readPos] == '\"')
|
||||
{
|
||||
readPos++; //Skip the ".
|
||||
|
||||
bool escapeChar = false;
|
||||
for (; ; readPos++)
|
||||
{
|
||||
if (readPos == paramString.Length)
|
||||
{
|
||||
throw new WikiFunctionPrototypeSyntaxError($"Expected end of string.");
|
||||
}
|
||||
else if (paramString[readPos] == '\\')
|
||||
{
|
||||
escapeChar = true;
|
||||
continue;
|
||||
}
|
||||
else if (paramString[readPos] == '\"' && escapeChar == false)
|
||||
{
|
||||
//Found the end of the string:
|
||||
readPos++; //Skip the ".
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
singleParam.Append(paramString[readPos]);
|
||||
}
|
||||
escapeChar = false;
|
||||
}
|
||||
|
||||
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
|
||||
}
|
||||
else if (paramString[readPos] == ',')
|
||||
{
|
||||
readPos++; //Skip the ,
|
||||
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
|
||||
|
||||
ps.Add(singleParam.ToString());
|
||||
singleParam.Clear();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
singleParam.Append(paramString[readPos]);
|
||||
|
||||
if (paramString[readPos] == '(')
|
||||
{
|
||||
readPos++;
|
||||
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
|
||||
}
|
||||
else if (paramString[readPos] == ')')
|
||||
{
|
||||
readPos++;
|
||||
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
readPos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ps.Count; i++)
|
||||
{
|
||||
ps[i] = ps[i].Trim();
|
||||
}
|
||||
|
||||
return ps;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
TightWiki.Engine.Function/FunctionPrototype.cs
Normal file
15
TightWiki.Engine.Function/FunctionPrototype.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public class FunctionPrototype
|
||||
{
|
||||
public string FunctionPrefix { get; set; } = string.Empty;
|
||||
public string ProperName { get; set; } = string.Empty;
|
||||
public string FunctionName { get; set; } = string.Empty;
|
||||
public List<PrototypeParameter> Parameters { get; set; }
|
||||
|
||||
public FunctionPrototype()
|
||||
{
|
||||
Parameters = new List<PrototypeParameter>();
|
||||
}
|
||||
}
|
||||
}
|
||||
207
TightWiki.Engine.Function/FunctionPrototypeCollection.cs
Normal file
207
TightWiki.Engine.Function/FunctionPrototypeCollection.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using TightWiki.Engine.Function.Exceptions;
|
||||
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public class FunctionPrototypeCollection
|
||||
{
|
||||
public enum WikiFunctionType
|
||||
{
|
||||
Standard,
|
||||
Scoped,
|
||||
Instruction
|
||||
}
|
||||
|
||||
public WikiFunctionType FunctionTypes { get; private set; }
|
||||
public List<PrototypeSet> Items { get; set; } = new();
|
||||
|
||||
public FunctionPrototypeCollection(WikiFunctionType functionTypes)
|
||||
{
|
||||
FunctionTypes = functionTypes;
|
||||
}
|
||||
|
||||
public void Add(string prototypeString)
|
||||
{
|
||||
var prototype = ParsePrototype(prototypeString);
|
||||
|
||||
Items.Add(new PrototypeSet()
|
||||
{
|
||||
FunctionPrefix = prototype.FunctionPrefix,
|
||||
ProperName = prototype.ProperName,
|
||||
FunctionName = prototype.FunctionName.ToLower(),
|
||||
Value = prototype
|
||||
});
|
||||
}
|
||||
|
||||
public bool Exists(string functionPrefix, string functionName)
|
||||
{
|
||||
functionName = functionName.ToLower();
|
||||
|
||||
//$$ are scope functions and are not called by prefix, we only have prefixes to make it easier to parse
|
||||
// the functions in the wikiText and scope functions are easy enough since they start with curly braces.
|
||||
return Items.Any(o => (o.FunctionPrefix == functionPrefix || o.FunctionPrefix == "$$") && o.FunctionName == functionName);
|
||||
}
|
||||
|
||||
public FunctionPrototype Get(string functionPrefix, string functionName)
|
||||
{
|
||||
functionName = functionName.ToLower();
|
||||
|
||||
//$$ are scope functions and are not called by prefix, we only have prefixes to make it easier to parse
|
||||
// the functions in the wikiText and scope functions are easy enough since they start with curly braces.
|
||||
var functionPrototype = Items.Where(o => (o.FunctionPrefix == functionPrefix || o.FunctionPrefix == "$$") && o.FunctionName == functionName).FirstOrDefault()?.Value;
|
||||
|
||||
return functionPrototype
|
||||
?? throw new WikiFunctionPrototypeNotDefinedException($"Function ({functionName}) does not have a defined prototype.");
|
||||
}
|
||||
|
||||
private FunctionPrototype ParsePrototype(string prototypeString)
|
||||
{
|
||||
int nameStartIndex = prototypeString.TakeWhile(c => char.IsLetterOrDigit(c) == false).Count();
|
||||
int nameEndIndex = prototypeString.IndexOf(':');
|
||||
string properName = prototypeString.Substring(nameStartIndex, nameEndIndex - nameStartIndex).Trim();
|
||||
string functionName = properName.ToLower();
|
||||
string functionPrefix = prototypeString.Substring(0, nameStartIndex).Trim();
|
||||
|
||||
prototypeString = prototypeString.Substring(nameEndIndex + 1).Trim();
|
||||
|
||||
var prototype = new FunctionPrototype() { FunctionPrefix = functionPrefix, ProperName = properName, FunctionName = functionName };
|
||||
|
||||
if (prototypeString.Length == 0)
|
||||
{
|
||||
//No parameters.
|
||||
return prototype;
|
||||
}
|
||||
|
||||
var segments = prototypeString.Trim().Split('|').Select(o => o.Trim());
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
var prototypeSegment = new PrototypeParameter();
|
||||
|
||||
int index = 0;
|
||||
|
||||
if (segment[index] == '<')
|
||||
{
|
||||
index++; //Skip the '<'
|
||||
prototypeSegment.Type = Tok(segment, ref index);
|
||||
index++; //Skip the '>'
|
||||
|
||||
if (prototypeSegment.Type.Contains(':'))
|
||||
{
|
||||
var splitSeg = prototypeSegment.Type.Split(':');
|
||||
prototypeSegment.Type = splitSeg[0];
|
||||
if (splitSeg[1].Equals("infinite", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
prototypeSegment.IsInfinite = true;
|
||||
if (prototype.Parameters.Any(o => o.IsInfinite))
|
||||
{
|
||||
throw new Exception($"Function [{functionName}], prototype error: cannot contain more than one [infinite] parameter.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Function [{functionName}], prototype error: expected [infinite] got [{splitSeg[1]}].");
|
||||
}
|
||||
}
|
||||
|
||||
SkipWhiteSpace(segment, ref index);
|
||||
|
||||
if (index < segment.Length && segment[index] == '{' || segment[index] == '[')
|
||||
{
|
||||
if (index < segment.Length && segment[index] == '[')
|
||||
{
|
||||
prototypeSegment.IsRequired = true;
|
||||
}
|
||||
|
||||
index++; //Skip the '[' or '{'
|
||||
|
||||
prototypeSegment.Name = Tok(segment, ref index);
|
||||
|
||||
if (index < segment.Length && segment[index] == '(') //Parse allowed values.
|
||||
{
|
||||
int allowedValueEndIndex = segment.IndexOf(')', index);
|
||||
string roteRequiredValues = segment.Substring(index + 1, allowedValueEndIndex - index - 1);
|
||||
prototypeSegment.AllowedValues = roteRequiredValues.Trim().Split(',').Select(o => o.Trim().ToLower()).ToList();
|
||||
|
||||
index = allowedValueEndIndex;
|
||||
index++; //Skip the ')'
|
||||
SkipWhiteSpace(segment, ref index);
|
||||
}
|
||||
|
||||
index++; //Skip the ']' or '}'
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Function [{functionName}], prototype error: expected [{{] or [[].");
|
||||
}
|
||||
|
||||
SkipWhiteSpace(segment, ref index);
|
||||
|
||||
if (index < segment.Length && segment[index] == '=')
|
||||
{
|
||||
index++; //Skip the '='
|
||||
SkipWhiteSpace(segment, ref index);
|
||||
|
||||
if (segment[index] != '\'')
|
||||
{
|
||||
throw new Exception($"Function [{functionName}], prototype error: expected [\'].");
|
||||
}
|
||||
|
||||
index++; //Skip the '''
|
||||
|
||||
prototypeSegment.DefaultValue = segment.Substring(index, (segment.Length - index) - 1);
|
||||
|
||||
index = segment.Length - 1;
|
||||
|
||||
if (index < segment.Length && segment[index] != '\'')
|
||||
{
|
||||
throw new Exception($"Function [{functionName}], prototype error: expected [\'].");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Function [{functionName}], prototype error: expected [<].");
|
||||
}
|
||||
|
||||
prototype.Parameters.Add(prototypeSegment);
|
||||
}
|
||||
|
||||
return prototype;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next token in a string.
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
private string Tok(string str, ref int index)
|
||||
{
|
||||
var token = string.Empty;
|
||||
|
||||
SkipWhiteSpace(str, ref index);
|
||||
|
||||
for (; index < str.Length; index++)
|
||||
{
|
||||
if ("<>{}[]()".Contains(str[index]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
token += str[index];
|
||||
}
|
||||
|
||||
SkipWhiteSpace(str, ref index);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private static void SkipWhiteSpace(string str, ref int index)
|
||||
{
|
||||
while (index < str.Length && char.IsWhiteSpace(str[index]))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
TightWiki.Engine.Function/NamedParameter.cs
Normal file
14
TightWiki.Engine.Function/NamedParameter.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public class NamedParameter
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
|
||||
public NamedParameter(string name, string value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
TightWiki.Engine.Function/OrdinalParameter.cs
Normal file
28
TightWiki.Engine.Function/OrdinalParameter.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public class OrdinalParameter
|
||||
{
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Has been matched to a prototype parameter?
|
||||
/// </summary>
|
||||
public bool IsMatched { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// If matched to a prototype parameter, this is the name of the parameter.
|
||||
/// </summary>
|
||||
public string ParameterName { get; set; } = string.Empty;
|
||||
|
||||
public OrdinalParameter(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public void AssociateWithPrototypeParam(string paramName)
|
||||
{
|
||||
IsMatched = true;
|
||||
ParameterName = paramName;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
TightWiki.Engine.Function/ParsedFunctionCall.cs
Normal file
18
TightWiki.Engine.Function/ParsedFunctionCall.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public class ParsedFunctionCall
|
||||
{
|
||||
public string Prefix { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int EndIndex { get; set; }
|
||||
public List<string> RawArguments { get; set; } = new List<string>();
|
||||
|
||||
public ParsedFunctionCall(string prefix, string name, int endIndex, List<string> rawArguments)
|
||||
{
|
||||
Prefix = prefix;
|
||||
Name = name;
|
||||
EndIndex = endIndex;
|
||||
RawArguments = rawArguments;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
TightWiki.Engine.Function/PrototypeParameter.cs
Normal file
12
TightWiki.Engine.Function/PrototypeParameter.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public class PrototypeParameter
|
||||
{
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string DefaultValue { get; set; } = string.Empty;
|
||||
public bool IsRequired { get; set; } = false;
|
||||
public bool IsInfinite { get; set; } = false;
|
||||
public List<string> AllowedValues { get; set; } = new();
|
||||
}
|
||||
}
|
||||
10
TightWiki.Engine.Function/PrototypeSet.cs
Normal file
10
TightWiki.Engine.Function/PrototypeSet.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public class PrototypeSet
|
||||
{
|
||||
public string FunctionPrefix { get; set; } = string.Empty;
|
||||
public string ProperName { get; set; } = string.Empty;
|
||||
public string FunctionName { get; set; } = string.Empty;
|
||||
public FunctionPrototype Value { get; set; } = new();
|
||||
}
|
||||
}
|
||||
176
TightWiki.Engine.Function/SelfDocument.cs
Normal file
176
TightWiki.Engine.Function/SelfDocument.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
namespace TightWiki.Engine.Function
|
||||
{
|
||||
public static class SelfDocument
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't ever look at this. :(
|
||||
/// </summary>
|
||||
public static void CreateNotExisting()
|
||||
{
|
||||
/*
|
||||
System.Threading.Thread.Sleep(500);
|
||||
foreach (var item in FunctionPrototypeDefinitions.Collection.Items)
|
||||
{
|
||||
string functionType = "Function";
|
||||
string functionPrefix = item.FunctionPrefix;
|
||||
|
||||
if (item.FunctionPrefix == "##")
|
||||
{
|
||||
functionType = "Standard Function";
|
||||
}
|
||||
if (item.FunctionPrefix == "@@")
|
||||
{
|
||||
functionType = "Instruction Function";
|
||||
}
|
||||
if (item.FunctionPrefix == "$$")
|
||||
{
|
||||
functionType = "Scope Function";
|
||||
functionPrefix = string.Empty;
|
||||
}
|
||||
|
||||
string topic = $"Wiki Help :: {item.ProperName}";
|
||||
|
||||
if (functionType == "Instruction Function")
|
||||
{
|
||||
topic = $"Wiki Help :: {item.ProperName} - Instruction";
|
||||
}
|
||||
|
||||
string navigation = CanonicalNavigation.CleanAndValidate(topic);
|
||||
|
||||
var page = PageRepository.GetPageInfoByNavigation(navigation);
|
||||
if (page == null)
|
||||
{
|
||||
var html = new System.Text.StringBuilder();
|
||||
html.AppendLine("@@draft");
|
||||
html.AppendLine("@@protect(true)");
|
||||
html.AppendLine("##Image(Wiki Help :: Wiki Help/TightWiki Logo.png, 15)");
|
||||
html.AppendLine($"##title ##Tag(Official-Help, Help, Wiki, Official, {functionType})");
|
||||
html.AppendLine("{{Card(Default, Table of Contents) ##toc }}");
|
||||
html.AppendLine("");
|
||||
html.AppendLine("${metaColor = #ee2401}");
|
||||
html.AppendLine("${keywordColor = #318000}");
|
||||
html.AppendLine("${identifierColor = #c6680e}");
|
||||
html.AppendLine("==Overview");
|
||||
html.AppendLine($"The {item.ProperName} {functionType.ToLower()} is !!FILL_IN_THE_BLANK!!");
|
||||
html.AppendLine("");
|
||||
html.AppendLine("");
|
||||
html.AppendLine("==Prototype");
|
||||
html.Append($"##Color(${{keywordColor}}, **#{{ {functionPrefix}{item.ProperName} }}#**)");
|
||||
if ((item.Value.Parameters?.Count ?? 0) == 0)
|
||||
{
|
||||
html.AppendLine("()");
|
||||
}
|
||||
else
|
||||
{
|
||||
html.Append("(");
|
||||
foreach (var p in item.Value.Parameters)
|
||||
{
|
||||
html.Append($"##Color(${{keywordColor}}, {p.Type}{(p.IsInfinite ? ":Infinite" : "")})");
|
||||
if (p.IsRequired)
|
||||
{
|
||||
html.Append($" [##Color(${{identifierColor}}, {p.Name})]");
|
||||
}
|
||||
else
|
||||
{
|
||||
html.Append($" {{##Color(${{identifierColor}}, {p.Name})}}");
|
||||
}
|
||||
html.Append(", ");
|
||||
}
|
||||
html.Length -= 3;
|
||||
html.Append(")");
|
||||
}
|
||||
|
||||
html.AppendLine("");
|
||||
html.AppendLine("");
|
||||
html.AppendLine("");
|
||||
html.AppendLine("===Parameters");
|
||||
html.AppendLine("{{Bullets");
|
||||
|
||||
if (item.Value.Parameters.Count == 0)
|
||||
{
|
||||
html.AppendLine($"None.");
|
||||
}
|
||||
|
||||
foreach (var p in item.Value.Parameters)
|
||||
{
|
||||
html.AppendLine($"**Name:** ##Color(${{identifierColor}}, {p.Name}) ##Color(${{metaColor}}, {(p.IsRequired ? "[Required]" : "{Optional}")})");
|
||||
html.AppendLine($">**Type:** ##Color(${{keywordColor}}, {p.Type}{(p.IsInfinite ? ":Infinite" : "")})");
|
||||
if (string.IsNullOrEmpty(p.DefaultValue) == false)
|
||||
{
|
||||
html.AppendLine($">**Default:** ##Color(${{identifierColor}}, {p.DefaultValue})");
|
||||
}
|
||||
if (p.AllowedValues != null)
|
||||
{
|
||||
html.AppendLine($">**Values:** ##Color(${{identifierColor}}, \"{string.Join(", ", p.AllowedValues)}\")");
|
||||
}
|
||||
html.AppendLine($">**Description:** !!FILL_IN_THE_BLANK!!");
|
||||
}
|
||||
html.AppendLine("}}");
|
||||
html.AppendLine("");
|
||||
|
||||
html.AppendLine("==Examples");
|
||||
html.AppendLine("{{Code(wiki)#{");
|
||||
|
||||
|
||||
if (item.FunctionPrefix == "$$")
|
||||
{
|
||||
html.Append("{{ " + $"{item.ProperName}");
|
||||
if ((item.Value.Parameters?.Count ?? 0) == 0)
|
||||
{
|
||||
html.AppendLine("()");
|
||||
}
|
||||
else
|
||||
{
|
||||
html.AppendLine($"({string.Join(", ", item.Value.Parameters.Select(o => o.Name))})");
|
||||
}
|
||||
|
||||
html.AppendLine("This is the body content of the function scope.");
|
||||
|
||||
html.AppendLine("}}");
|
||||
}
|
||||
else
|
||||
{
|
||||
html.Append($"{item.FunctionPrefix}{item.ProperName}");
|
||||
if ((item.Value.Parameters?.Count ?? 0) == 0)
|
||||
{
|
||||
html.AppendLine("()");
|
||||
}
|
||||
else
|
||||
{
|
||||
html.AppendLine($"({string.Join(", ", item.Value.Parameters.Select(o => o.Name))})");
|
||||
}
|
||||
}
|
||||
|
||||
html.AppendLine("}#}}");
|
||||
html.AppendLine("");
|
||||
|
||||
html.AppendLine("==See Also");
|
||||
html.AppendLine("[[Wiki Help :: Function Calling Convention]]");
|
||||
html.AppendLine("[[Wiki Help :: Scope Function]]");
|
||||
html.AppendLine("[[Wiki Help :: Instruction Function]]");
|
||||
html.AppendLine("[[Wiki Help :: Standard Function]]");
|
||||
html.AppendLine("");
|
||||
html.AppendLine("");
|
||||
|
||||
html.AppendLine("==Related");
|
||||
html.AppendLine("##related");
|
||||
|
||||
page = new Page()
|
||||
{
|
||||
Navigation = navigation,
|
||||
Name = topic,
|
||||
Description = $"Documentation of the built-in {item.ProperName.ToLower()} {functionType.ToLower()} !!FILL_IN_THE_BLANK!!.",
|
||||
CreatedByUserId = 1,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
ModifiedByUserId = 1,
|
||||
ModifiedDate = DateTime.UtcNow,
|
||||
Body = html.ToString()
|
||||
};
|
||||
|
||||
PageRepository.SavePage(page);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
18
TightWiki.Engine.Function/TightWiki.Engine.Function.csproj
Normal file
18
TightWiki.Engine.Function/TightWiki.Engine.Function.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Version>2.20.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
<DebugType>None</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NTDLS.Helpers" Version="1.3.11" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user