Я пытаюсь создать игру (с использованием движка Unity 3D), которая использует терминал / оболочку для взаимодействия (почти) со всем. Здесь также возникает множество проблем, потому что реализация должна быть одновременно жесткой, гибкой, простой и расширяемой.
На данный момент меня не очень интересует эстетика терминала - только функциональность оболочки - и поэтому я сосредоточен на создании типа команды, который может быть создан из строки, которая анализируется на общий тип аргумента. а затем преобразован во внутренний тип (например, string
, int
и т. д.).
Ниже я привел код класса Command
. Я очень усердно работаю над этим, но я застрял, пытаясь разобрать это на какое-то действие, которое в конечном итоге может быть выполнено. Проблема в том, что ArgumentType
внутри класса Argument
не хочет кастомизироваться, и я не нашел хорошего решения для этого. Может ли кто-нибудь помочь мне решить эту проблему или указать направление работы? Заранее спасибо!
namespace Terminal.Shells
{
public abstract class Command
{
private readonly Action<ArgumentType []> procedure;
private readonly CommandManual manual;
public IEnumerable<Type> Parse (string line)
{
for (int i = 0; i < manual.RequiredArguments.Length; i++)
{
var word = EatNextWord (ref line);
if (string.IsNullOrEmpty (word))
throw new System.Exception (); // TODO: index out of range
var argumentType = manual.RequiredArguments [i].Type;
// ### conversion from string to type
}
// TODO: Optional Arguments
}
public string EatNextWord (ref string line) { /* ... */ }
}
}
Класс CommandManual
содержит только два интересных свойства:
internal readonly Argument [] RequiredArguments;
internal readonly Argument [] OptionalArguments;
Класс Argument
:
namespace Terminal.Shells
{
public struct Argument
{
private readonly string identifier;
private readonly ArgumentType type;
public string Identifier => this.identifier;
public ArgumentType Type => this.type;
// ...
}
}
Я создал ArgumentType
и ArgumentType<T>
: первый действует как класс Enumeration
и может использоваться везде, последний используется для фактического преобразования из входных (строковых) в связанные типы.
namespace Terminal.Shells
{
public abstract partial class ArgumentType : Enumeration
{
public static readonly ArgumentType Command = new CommandType ();
public static readonly ArgumentType Boolean = new BooleanType ();
public static readonly ArgumentType String = new StringType ();
public static readonly ArgumentType Range = new RangeType ();
protected ArgumentType (string name, int value)
: base (name, value) { }
// ### THIS GOES WRONG
public ArgumentType<T> GenericType => ???;
}
public abstract partial class ArgumentType<T> : ArgumentType
{
protected ArgumentType (string name, int value)
: base (name, value) { }
/// <summary>
/// Tries to cast the value of an argument in this wrapper
/// to an actual primitive value enclosed in a <see cref = "Maybe{T}"/>.
/// </summary>
/// <returns>A <see cref = "Maybe{T}"/> value that might contain the
/// casted value.</returns>
public abstract Maybe<T> TryCast (string input);
/// <summary>
/// Casts the value of an argument in this wrapper to
/// an actual primitive value or throws an error.
/// </summary>
/// <returns>An <see cref = "Either{T, ArgumentTypeException}"/> value
/// that contains the casted value or an <see cref = "ArgumentTypeException"/>.
/// </returns>
public virtual Either<T, ArgumentTypeException> Cast (string input)
{
return this
.TryCast (input)
.ValueOr (new ArgumentTypeException (
typeof (T),
typeof (void)
));
}
}
}
Одним из примеров этого универсального типа перечисления является класс IntegerType
:
namespace Terminal.Shells
{
public class IntegerType : ArgumentType<int>
{
public IntegerType () : base ("integer", 4) { }
/// <inheritdoc/>
public override Maybe<int> TryCast (string input)
{
int.TryParse (input, out var result);
return result;
}
}
}