Files
Mementos/Unity/Alternate Genre Jam/Assets/Ink/InkLibs/InkRuntime/Value.cs
2021-06-30 21:39:19 +10:00

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);
}
}
}