mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 02:24:28 +11:00
406 lines
12 KiB
C#
406 lines
12 KiB
C#
using System.ComponentModel;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Ink.Runtime
|
|
{
|
|
// Order is significant for type coersion.
|
|
// If types aren't directly compatible for an operation,
|
|
// they're coerced to the same type, downward.
|
|
// Higher value types "infect" an operation.
|
|
// (This may not be the most sensible thing to do, but it's worked so far!)
|
|
public enum ValueType
|
|
{
|
|
// Bool is new addition, keep enum values the same, with Int==0, Float==1 etc,
|
|
// but for coersion rules, we want to keep bool with a lower value than Int
|
|
// so that it converts in the right direction
|
|
Bool = -1,
|
|
// Used in coersion
|
|
Int,
|
|
Float,
|
|
List,
|
|
String,
|
|
|
|
// Not used for coersion described above
|
|
DivertTarget,
|
|
VariablePointer
|
|
}
|
|
|
|
public abstract class Value : Runtime.Object
|
|
{
|
|
public abstract ValueType valueType { get; }
|
|
public abstract bool isTruthy { get; }
|
|
|
|
public abstract Value Cast(ValueType newType);
|
|
|
|
public abstract object valueObject { get; }
|
|
|
|
public static Value Create(object val)
|
|
{
|
|
// Implicitly lose precision from any doubles we get passed in
|
|
if (val is double) {
|
|
double doub = (double)val;
|
|
val = (float)doub;
|
|
}
|
|
|
|
if( val is bool ) {
|
|
return new BoolValue((bool)val);
|
|
} else if (val is int) {
|
|
return new IntValue ((int)val);
|
|
} else if (val is long) {
|
|
return new IntValue ((int)(long)val);
|
|
} else if (val is float) {
|
|
return new FloatValue ((float)val);
|
|
} else if (val is double) {
|
|
return new FloatValue ((float)(double)val);
|
|
} else if (val is string) {
|
|
return new StringValue ((string)val);
|
|
} else if (val is Path) {
|
|
return new DivertTargetValue ((Path)val);
|
|
} else if (val is InkList) {
|
|
return new ListValue ((InkList)val);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override Object Copy()
|
|
{
|
|
return Create (valueObject);
|
|
}
|
|
|
|
protected StoryException BadCastException (ValueType targetType)
|
|
{
|
|
return new StoryException ("Can't cast "+this.valueObject+" from " + this.valueType+" to "+targetType);
|
|
}
|
|
}
|
|
|
|
public abstract class Value<T> : Value
|
|
{
|
|
public T value { get; set; }
|
|
|
|
public override object valueObject {
|
|
get {
|
|
return (object)value;
|
|
}
|
|
}
|
|
|
|
public Value (T val)
|
|
{
|
|
value = val;
|
|
}
|
|
|
|
public override string ToString ()
|
|
{
|
|
return value.ToString();
|
|
}
|
|
}
|
|
|
|
public class BoolValue : Value<bool>
|
|
{
|
|
public override ValueType valueType { get { return ValueType.Bool; } }
|
|
public override bool isTruthy { get { return value; } }
|
|
|
|
public BoolValue(bool boolVal) : base(boolVal)
|
|
{
|
|
}
|
|
|
|
public BoolValue() : this(false) {}
|
|
|
|
public override Value Cast(ValueType newType)
|
|
{
|
|
if (newType == valueType) {
|
|
return this;
|
|
}
|
|
|
|
if (newType == ValueType.Int) {
|
|
return new IntValue (this.value ? 1 : 0);
|
|
}
|
|
|
|
if (newType == ValueType.Float) {
|
|
return new FloatValue (this.value ? 1.0f : 0.0f);
|
|
}
|
|
|
|
if (newType == ValueType.String) {
|
|
return new StringValue(this.value ? "true" : "false");
|
|
}
|
|
|
|
throw BadCastException (newType);
|
|
}
|
|
|
|
public override string ToString ()
|
|
{
|
|
// Instead of C# "True" / "False"
|
|
return value ? "true" : "false";
|
|
}
|
|
}
|
|
|
|
public class IntValue : Value<int>
|
|
{
|
|
public override ValueType valueType { get { return ValueType.Int; } }
|
|
public override bool isTruthy { get { return value != 0; } }
|
|
|
|
public IntValue(int intVal) : base(intVal)
|
|
{
|
|
}
|
|
|
|
public IntValue() : this(0) {}
|
|
|
|
public override Value Cast(ValueType newType)
|
|
{
|
|
if (newType == valueType) {
|
|
return this;
|
|
}
|
|
|
|
if (newType == ValueType.Bool) {
|
|
return new BoolValue (this.value == 0 ? false : true);
|
|
}
|
|
|
|
if (newType == ValueType.Float) {
|
|
return new FloatValue ((float)this.value);
|
|
}
|
|
|
|
if (newType == ValueType.String) {
|
|
return new StringValue("" + this.value);
|
|
}
|
|
|
|
throw BadCastException (newType);
|
|
}
|
|
}
|
|
|
|
public class FloatValue : Value<float>
|
|
{
|
|
public override ValueType valueType { get { return ValueType.Float; } }
|
|
public override bool isTruthy { get { return value != 0.0f; } }
|
|
|
|
public FloatValue(float val) : base(val)
|
|
{
|
|
}
|
|
|
|
public FloatValue() : this(0.0f) {}
|
|
|
|
public override Value Cast(ValueType newType)
|
|
{
|
|
if (newType == valueType) {
|
|
return this;
|
|
}
|
|
|
|
if (newType == ValueType.Bool) {
|
|
return new BoolValue (this.value == 0.0f ? false : true);
|
|
}
|
|
|
|
if (newType == ValueType.Int) {
|
|
return new IntValue ((int)this.value);
|
|
}
|
|
|
|
if (newType == ValueType.String) {
|
|
return new StringValue("" + this.value.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
throw BadCastException (newType);
|
|
}
|
|
}
|
|
|
|
public class StringValue : Value<string>
|
|
{
|
|
public override ValueType valueType { get { return ValueType.String; } }
|
|
public override bool isTruthy { get { return value.Length > 0; } }
|
|
|
|
public bool isNewline { get; private set; }
|
|
public bool isInlineWhitespace { get; private set; }
|
|
public bool isNonWhitespace {
|
|
get {
|
|
return !isNewline && !isInlineWhitespace;
|
|
}
|
|
}
|
|
|
|
public StringValue(string str) : base(str)
|
|
{
|
|
// Classify whitespace status
|
|
isNewline = value == "\n";
|
|
isInlineWhitespace = true;
|
|
foreach (var c in value) {
|
|
if (c != ' ' && c != '\t') {
|
|
isInlineWhitespace = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public StringValue() : this("") {}
|
|
|
|
public override Value Cast(ValueType newType)
|
|
{
|
|
if (newType == valueType) {
|
|
return this;
|
|
}
|
|
|
|
if (newType == ValueType.Int) {
|
|
|
|
int parsedInt;
|
|
if (int.TryParse (value, out parsedInt)) {
|
|
return new IntValue (parsedInt);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (newType == ValueType.Float) {
|
|
float parsedFloat;
|
|
if (float.TryParse (value, System.Globalization.NumberStyles.Float ,System.Globalization.CultureInfo.InvariantCulture, out parsedFloat)) {
|
|
return new FloatValue (parsedFloat);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
throw BadCastException (newType);
|
|
}
|
|
}
|
|
|
|
public class DivertTargetValue : Value<Path>
|
|
{
|
|
public Path targetPath { get { return this.value; } set { this.value = value; } }
|
|
public override ValueType valueType { get { return ValueType.DivertTarget; } }
|
|
public override bool isTruthy { get { throw new System.Exception("Shouldn't be checking the truthiness of a divert target"); } }
|
|
|
|
public DivertTargetValue(Path targetPath) : base(targetPath)
|
|
{
|
|
}
|
|
|
|
public DivertTargetValue() : base(null)
|
|
{}
|
|
|
|
public override Value Cast(ValueType newType)
|
|
{
|
|
if (newType == valueType)
|
|
return this;
|
|
|
|
throw BadCastException (newType);
|
|
}
|
|
|
|
public override string ToString ()
|
|
{
|
|
return "DivertTargetValue(" + targetPath + ")";
|
|
}
|
|
}
|
|
|
|
// TODO: Think: Erm, I get that this contains a string, but should
|
|
// we really derive from Value<string>? That seems a bit misleading to me.
|
|
public class VariablePointerValue : Value<string>
|
|
{
|
|
public string variableName { get { return this.value; } set { this.value = value; } }
|
|
public override ValueType valueType { get { return ValueType.VariablePointer; } }
|
|
public override bool isTruthy { get { throw new System.Exception("Shouldn't be checking the truthiness of a variable pointer"); } }
|
|
|
|
// Where the variable is located
|
|
// -1 = default, unknown, yet to be determined
|
|
// 0 = in global scope
|
|
// 1+ = callstack element index + 1 (so that the first doesn't conflict with special global scope)
|
|
public int contextIndex { get; set; }
|
|
|
|
public VariablePointerValue(string variableName, int contextIndex = -1) : base(variableName)
|
|
{
|
|
this.contextIndex = contextIndex;
|
|
}
|
|
|
|
public VariablePointerValue() : this(null)
|
|
{
|
|
}
|
|
|
|
public override Value Cast(ValueType newType)
|
|
{
|
|
if (newType == valueType)
|
|
return this;
|
|
|
|
throw BadCastException (newType);
|
|
}
|
|
|
|
public override string ToString ()
|
|
{
|
|
return "VariablePointerValue(" + variableName + ")";
|
|
}
|
|
|
|
public override Object Copy()
|
|
{
|
|
return new VariablePointerValue (variableName, contextIndex);
|
|
}
|
|
}
|
|
|
|
public class ListValue : Value<InkList>
|
|
{
|
|
public override ValueType valueType {
|
|
get {
|
|
return ValueType.List;
|
|
}
|
|
}
|
|
|
|
// Truthy if it is non-empty
|
|
public override bool isTruthy {
|
|
get {
|
|
return value.Count > 0;
|
|
}
|
|
}
|
|
|
|
public override Value Cast (ValueType newType)
|
|
{
|
|
if (newType == ValueType.Int) {
|
|
var max = value.maxItem;
|
|
if( max.Key.isNull )
|
|
return new IntValue (0);
|
|
else
|
|
return new IntValue (max.Value);
|
|
}
|
|
|
|
else if (newType == ValueType.Float) {
|
|
var max = value.maxItem;
|
|
if (max.Key.isNull)
|
|
return new FloatValue (0.0f);
|
|
else
|
|
return new FloatValue ((float)max.Value);
|
|
}
|
|
|
|
else if (newType == ValueType.String) {
|
|
var max = value.maxItem;
|
|
if (max.Key.isNull)
|
|
return new StringValue ("");
|
|
else {
|
|
return new StringValue (max.Key.ToString());
|
|
}
|
|
}
|
|
|
|
if (newType == valueType)
|
|
return this;
|
|
|
|
throw BadCastException (newType);
|
|
}
|
|
|
|
public ListValue () : base(null) {
|
|
value = new InkList ();
|
|
}
|
|
|
|
public ListValue (InkList list) : base (null)
|
|
{
|
|
value = new InkList (list);
|
|
}
|
|
|
|
public ListValue (InkListItem singleItem, int singleValue) : base (null)
|
|
{
|
|
value = new InkList {
|
|
{singleItem, singleValue}
|
|
};
|
|
}
|
|
|
|
public static void RetainListOriginsForAssignment (Runtime.Object oldValue, Runtime.Object newValue)
|
|
{
|
|
var oldList = oldValue as ListValue;
|
|
var newList = newValue as ListValue;
|
|
|
|
// When assigning the emtpy list, try to retain any initial origin names
|
|
if (oldList && newList && newList.value.Count == 0)
|
|
newList.value.SetInitialOriginNames (oldList.value.originNames);
|
|
}
|
|
}
|
|
|
|
}
|
|
|