Created
August 24, 2016 12:49
-
-
Save renaudbedard/6923bc79cc60b72e76b4566781ee11ee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Globalization; | |
using UnityEngine; | |
namespace Yarn | |
{ | |
// A value from inside Yarn. | |
public struct Value : IComparable, IComparable<Value>, IEquatable<Value> | |
{ | |
internal readonly Type type; | |
// The underlying values for this object | |
internal readonly float numberValue; | |
internal readonly string variableName; | |
internal readonly string stringValue; | |
internal readonly bool boolValue; | |
public enum Type | |
{ | |
Null, // the null value | |
Number, // a constant number | |
String, // a string | |
Bool, // a boolean value | |
Variable // the name of a variable; will be expanded at runtime | |
} | |
public static readonly Value NULL = new Value(); | |
// Create as specific value type (mainly for variables) | |
public Value(Type type, string value) | |
{ | |
// default values | |
numberValue = 0; | |
stringValue = null; | |
variableName = null; | |
boolValue = false; | |
this.type = type; | |
if (type == Type.String) | |
stringValue = value; | |
else if (type == Type.Variable) | |
variableName = value; | |
else | |
throw new InvalidOperationException("String value constructor expects a variable or string value type"); | |
} | |
// Garbage-less simple constructors | |
public Value(float value) | |
{ | |
variableName = null; | |
stringValue = null; | |
boolValue = false; | |
type = Type.Number; | |
numberValue = value; | |
} | |
public Value(string value) | |
{ | |
variableName = null; | |
numberValue = 0; | |
boolValue = false; | |
type = Type.String; | |
stringValue = value; | |
} | |
public Value(bool value) | |
{ | |
variableName = null; | |
stringValue = null; | |
numberValue = 0; | |
type = Type.Bool; | |
boolValue = value; | |
} | |
// Create a value with a C# object | |
public Value(object value) | |
{ | |
// default values | |
numberValue = 0; | |
variableName = null; | |
stringValue = null; | |
boolValue = false; | |
type = Type.Null; | |
// Copy an existing value | |
if (value is Value) | |
{ | |
var otherValue = (Value) value; | |
type = otherValue.type; | |
switch (type) | |
{ | |
case Type.Number: | |
numberValue = otherValue.numberValue; | |
break; | |
case Type.String: | |
stringValue = otherValue.stringValue; | |
break; | |
case Type.Bool: | |
boolValue = otherValue.boolValue; | |
break; | |
case Type.Variable: | |
variableName = otherValue.variableName; | |
break; | |
case Type.Null: | |
break; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
return; | |
} | |
if (value == null) | |
{ | |
type = Type.Null; | |
return; | |
} | |
if (value is string) | |
{ | |
type = Type.String; | |
stringValue = Convert.ToString(value); | |
return; | |
} | |
if (value is int || value is float || value is double) | |
{ | |
type = Type.Number; | |
numberValue = Convert.ToSingle(value); | |
return; | |
} | |
if (value is bool) | |
{ | |
type = Type.Bool; | |
boolValue = Convert.ToBoolean(value); | |
return; | |
} | |
string error = string.Format("Attempted to create a Value using a {0}; currently, " + | |
"Values can only be numbers, strings, bools or null.", value.GetType().Name); | |
throw new YarnException(error); | |
} | |
object backingValue | |
{ | |
get | |
{ | |
switch (type) | |
{ | |
case Type.Null: | |
return null; | |
case Type.String: | |
return stringValue; | |
case Type.Number: | |
return numberValue; | |
case Type.Bool: | |
return boolValue; | |
} | |
throw new InvalidOperationException(string.Format("Can't get good backing type for {0}", type)); | |
} | |
} | |
public float AsNumber | |
{ | |
get | |
{ | |
switch (type) | |
{ | |
case Type.Number: | |
return numberValue; | |
case Type.String: | |
try | |
{ | |
return float.Parse(stringValue); | |
} | |
catch (FormatException) | |
{ | |
return 0.0f; | |
} | |
case Type.Bool: | |
return boolValue ? 1.0f : 0.0f; | |
case Type.Null: | |
return 0.0f; | |
default: | |
throw new InvalidOperationException("Cannot cast to number from " + type); | |
} | |
} | |
} | |
public bool AsBool | |
{ | |
get | |
{ | |
switch (type) | |
{ | |
case Type.Number: | |
return !float.IsNaN(numberValue) && !Mathf.Approximately(numberValue, 0); | |
case Type.String: | |
return !string.IsNullOrEmpty(stringValue); | |
case Type.Bool: | |
return boolValue; | |
case Type.Null: | |
return false; | |
default: | |
throw new InvalidOperationException("Cannot cast to bool from " + type); | |
} | |
} | |
} | |
public string AsString | |
{ | |
get | |
{ | |
switch (type) | |
{ | |
case Type.Number: | |
if (float.IsNaN(numberValue)) | |
return "NaN"; | |
return numberValue.ToString(CultureInfo.InvariantCulture); | |
case Type.String: | |
return stringValue; | |
case Type.Bool: | |
return boolValue.ToString(); | |
case Type.Null: | |
return "null"; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
} | |
} | |
public int CompareTo(object obj) | |
{ | |
if (obj == null) return 1; | |
// soft, fast coercion | |
if (!(obj is Value)) | |
throw new ArgumentException("Object is not a Value"); | |
var other = (Value) obj; | |
// it is a value! | |
return CompareTo(other); | |
} | |
public int CompareTo(Value other) | |
{ | |
if (other.type == type) | |
{ | |
switch (type) | |
{ | |
case Type.Null: | |
return 0; | |
case Type.String: | |
return string.Compare(stringValue, other.stringValue, StringComparison.Ordinal); | |
case Type.Number: | |
return numberValue.CompareTo(other.numberValue); | |
case Type.Bool: | |
return boolValue.CompareTo(other.boolValue); | |
} | |
} | |
// try to do a string test at that point! | |
return string.Compare(AsString, other.AsString, StringComparison.Ordinal); | |
} | |
public bool Equals(Value other) | |
{ | |
switch (type) | |
{ | |
case Type.Number: | |
return Mathf.Approximately(AsNumber, other.AsNumber); | |
case Type.String: | |
return AsString == other.AsString; | |
case Type.Bool: | |
return AsBool == other.AsBool; | |
case Type.Null: | |
return other.type == Type.Null || Mathf.Approximately(other.AsNumber, 0) || other.AsBool == false; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
} | |
public override bool Equals(object obj) | |
{ | |
if (!(obj is Value)) | |
return false; | |
var other = (Value) obj; | |
return Equals(other); | |
} | |
public static bool operator==(Value a, Value b) | |
{ | |
return a.Equals(b); | |
} | |
public static bool operator !=(Value a, Value b) | |
{ | |
return !(a == b); | |
} | |
// override object.GetHashCode | |
public override int GetHashCode() | |
{ | |
var backing = backingValue; | |
if (backing != null) | |
return backing.GetHashCode(); | |
return 0; | |
} | |
public override string ToString() | |
{ | |
return string.Format("[Value: type={0}, AsNumber={1}, AsBool={2}, AsString={3}]", type, AsNumber, AsBool, AsString); | |
} | |
public static Value operator +(Value a, Value b) | |
{ | |
// catches: | |
// undefined + string | |
// number + string | |
// string + string | |
// bool + string | |
// null + string | |
if (a.type == Type.String || b.type == Type.String) | |
{ | |
// we're headed for string town! | |
return new Value(a.AsString + b.AsString); | |
} | |
// catches: | |
// number + number | |
// bool (=> 0 or 1) + number | |
// null (=> 0) + number | |
// bool (=> 0 or 1) + bool (=> 0 or 1) | |
// null (=> 0) + null (=> 0) | |
if (a.type == Type.Number || b.type == Type.Number || | |
(a.type == Type.Bool && b.type == Type.Bool) || | |
(a.type == Type.Null && b.type == Type.Null)) | |
{ | |
return new Value(a.AsNumber + b.AsNumber); | |
} | |
throw new ArgumentException(string.Format("Cannot add types {0} and {1}.", a.type, b.type)); | |
} | |
public static Value operator -(Value a, Value b) | |
{ | |
if (a.type == Type.Number && (b.type == Type.Number || b.type == Type.Null) || | |
b.type == Type.Number && (a.type == Type.Number || a.type == Type.Null)) | |
{ | |
return new Value(a.AsNumber - b.AsNumber); | |
} | |
throw new ArgumentException(string.Format("Cannot subtract types {0} and {1}.", a.type, b.type)); | |
} | |
public static Value operator *(Value a, Value b) | |
{ | |
if (a.type == Type.Number && (b.type == Type.Number || b.type == Type.Null) || | |
b.type == Type.Number && (a.type == Type.Number || a.type == Type.Null)) | |
{ | |
return new Value(a.AsNumber * b.AsNumber); | |
} | |
throw new ArgumentException(string.Format("Cannot multiply types {0} and {1}.", a.type, b.type)); | |
} | |
public static Value operator /(Value a, Value b) | |
{ | |
if (a.type == Type.Number && (b.type == Type.Number || b.type == Type.Null) || | |
b.type == Type.Number && (a.type == Type.Number || a.type == Type.Null)) | |
{ | |
return new Value(a.AsNumber / b.AsNumber); | |
} | |
throw new ArgumentException(string.Format("Cannot divide types {0} and {1}.", a.type, b.type)); | |
} | |
public static Value operator -(Value a) | |
{ | |
if (a.type == Type.Number) | |
return new Value(-a.AsNumber); | |
if (a.type == Type.Null && | |
a.type == Type.String && | |
(a.AsString == null || a.AsString.Trim() == "")) | |
{ | |
return new Value(-0); | |
} | |
return new Value(float.NaN); | |
} | |
// Define the is greater than operator. | |
public static bool operator >(Value operand1, Value operand2) | |
{ | |
return operand1.CompareTo(operand2) == 1; | |
} | |
// Define the is less than operator. | |
public static bool operator <(Value operand1, Value operand2) | |
{ | |
return operand1.CompareTo(operand2) == -1; | |
} | |
// Define the is greater than or equal to operator. | |
public static bool operator >=(Value operand1, Value operand2) | |
{ | |
return operand1.CompareTo(operand2) >= 0; | |
} | |
// Define the is less than or equal to operator. | |
public static bool operator <=(Value operand1, Value operand2) | |
{ | |
return operand1.CompareTo(operand2) <= 0; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome!