Flash/Core/Value.cs
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Flash;
public enum ValueKind : byte { Undefined, Null, Bool, Number, String, Object }
/// <summary>An ActionScript 1/2 value (ECMA-262 3rd edition rules, as Flash Player 7+ uses them).</summary>
public readonly struct Value
{
public readonly ValueKind Kind;
public readonly double Num;
public readonly string Str;
public readonly AsObject Obj;
Value( ValueKind k, double n = 0, string s = null, AsObject o = null ) { Kind = k; Num = n; Str = s; Obj = o; }
public static readonly Value Undefined = new( ValueKind.Undefined );
public static readonly Value Null = new( ValueKind.Null );
public static readonly Value True = new( ValueKind.Bool, 1 );
public static readonly Value False = new( ValueKind.Bool, 0 );
public static readonly Value NaN = new( ValueKind.Number, double.NaN );
public static readonly Value Zero = new( ValueKind.Number, 0 );
public static readonly Value EmptyString = new( ValueKind.String, 0, "" );
public static Value From( double d ) => new( ValueKind.Number, d );
public static Value From( int d ) => new( ValueKind.Number, d );
public static Value From( bool b ) => b ? True : False;
public static Value From( string s ) => s is null ? Undefined : new( ValueKind.String, 0, s );
public static Value From( AsObject o ) => o is null ? Null : new( ValueKind.Object, 0, null, o );
public static implicit operator Value( double d ) => From( d );
public static implicit operator Value( int d ) => From( d );
public static implicit operator Value( bool b ) => From( b );
public static implicit operator Value( string s ) => From( s );
public static implicit operator Value( AsObject o ) => From( o );
public bool IsUndefined => Kind == ValueKind.Undefined;
public bool IsNull => Kind == ValueKind.Null;
public bool IsNullish => Kind == ValueKind.Undefined || Kind == ValueKind.Null;
public bool IsObject => Kind == ValueKind.Object;
public bool IsString => Kind == ValueKind.String;
public bool IsNumber => Kind == ValueKind.Number;
public bool IsBool => Kind == ValueKind.Bool;
public bool BoolValue => Num != 0;
public AsObject AsObj => Kind == ValueKind.Object ? Obj : null;
// ---------------------------------------------------------------- conversions
public bool ToBool( int swfVersion = 7 )
{
switch ( Kind )
{
case ValueKind.Bool: return Num != 0;
case ValueKind.Number: return !double.IsNaN( Num ) && Num != 0;
case ValueKind.String:
if ( swfVersion >= 7 ) return Str.Length > 0;
var n = StringToNumber( Str, swfVersion );
return !double.IsNaN( n ) && n != 0;
case ValueKind.Object: return true;
default: return false;
}
}
public double ToNumber( Avm1 vm )
{
switch ( Kind )
{
case ValueKind.Number: return Num;
case ValueKind.Bool: return Num;
case ValueKind.String: return StringToNumber( Str, vm?.SwfVersion ?? 7 );
case ValueKind.Undefined: return (vm?.SwfVersion ?? 7) >= 7 ? double.NaN : 0;
case ValueKind.Null: return (vm?.SwfVersion ?? 7) >= 7 ? double.NaN : 0;
case ValueKind.Object:
if ( Obj is DisplayObject ) return double.NaN;
var p = Obj.ToPrimitive( vm, hintString: false );
if ( p.Kind == ValueKind.Object ) return double.NaN;
return p.ToNumber( vm );
}
return double.NaN;
}
public string ToStr( Avm1 vm )
{
switch ( Kind )
{
case ValueKind.String: return Str;
case ValueKind.Number: return NumberToString( Num );
case ValueKind.Bool: return Num != 0 ? "true" : "false";
case ValueKind.Undefined: return (vm?.SwfVersion ?? 7) >= 7 ? "undefined" : "";
case ValueKind.Null: return "null";
case ValueKind.Object:
if ( Obj is DisplayObject d && !d.HasOwnToString ) return d.TargetPath;
var p = Obj.ToPrimitive( vm, hintString: true );
if ( p.Kind == ValueKind.Object ) return p.Obj is AsFunction ? "[type Function]" : "[object Object]";
return p.ToStr( vm );
}
return "";
}
public int ToInt32( Avm1 vm ) => ToInt32( ToNumber( vm ) );
public static int ToInt32( double d )
{
if ( double.IsNaN( d ) || double.IsInfinity( d ) ) return 0;
d = Math.Truncate( d );
if ( d >= int.MinValue && d <= int.MaxValue ) return (int)d;
double m = d % 4294967296.0;
if ( m < 0 ) m += 4294967296.0;
return (int)(uint)m;
}
public static uint ToUInt32( double d ) => (uint)ToInt32( d );
public static double StringToNumber( string s, int swfVersion )
{
if ( s is null ) return double.NaN;
var t = s.Trim( ' ', '\t', '\n', '\r', '\f', '\v' );
if ( t.Length == 0 ) return swfVersion >= 5 ? double.NaN : 0;
if ( t.Length > 2 && t[0] == '0' && (t[1] == 'x' || t[1] == 'X') )
{
long v = 0;
for ( int i = 2; i < t.Length; i++ )
{
int h = HexDigit( t[i] );
if ( h < 0 ) return double.NaN;
v = v * 16 + h;
}
return v;
}
if ( t == "Infinity" || t == "+Infinity" ) return double.PositiveInfinity;
if ( t == "-Infinity" ) return double.NegativeInfinity;
// strict decimal literal
int k = 0;
if ( k < t.Length && (t[k] == '+' || t[k] == '-') ) k++;
bool digits = false;
while ( k < t.Length && char.IsDigit( t[k] ) ) { k++; digits = true; }
if ( k < t.Length && t[k] == '.' ) { k++; while ( k < t.Length && char.IsDigit( t[k] ) ) { k++; digits = true; } }
if ( !digits ) return double.NaN;
if ( k < t.Length && (t[k] == 'e' || t[k] == 'E') )
{
int e = k + 1;
if ( e < t.Length && (t[e] == '+' || t[e] == '-') ) e++;
int ds = e;
while ( e < t.Length && char.IsDigit( t[e] ) ) e++;
if ( e == ds ) return double.NaN;
k = e;
}
if ( k != t.Length ) return double.NaN;
return double.TryParse( t, NumberStyles.Float, CultureInfo.InvariantCulture, out var r ) ? r : double.NaN;
}
static int HexDigit( char c )
{
if ( c >= '0' && c <= '9' ) return c - '0';
if ( c >= 'a' && c <= 'f' ) return c - 'a' + 10;
if ( c >= 'A' && c <= 'F' ) return c - 'A' + 10;
return -1;
}
/// <summary>Flash's Number→String (15 significant digits, exponent form outside 1e-5..1e15).</summary>
public static string NumberToString( double d )
{
if ( double.IsNaN( d ) ) return "NaN";
if ( double.IsPositiveInfinity( d ) ) return "Infinity";
if ( double.IsNegativeInfinity( d ) ) return "-Infinity";
if ( d == 0 ) return "0";
if ( Math.Abs( d ) < 1e15 && d == Math.Floor( d ) ) return ((long)d).ToString( CultureInfo.InvariantCulture );
var s = d.ToString( "G15", CultureInfo.InvariantCulture );
int e = s.IndexOf( 'E' );
if ( e < 0 ) return s;
var mant = s.Substring( 0, e );
int exp = int.Parse( s.Substring( e + 1 ), CultureInfo.InvariantCulture );
if ( exp >= -5 && exp < 15 )
return d.ToString( "0.###############", CultureInfo.InvariantCulture );
return mant + "e" + (exp >= 0 ? "+" : "-") + Math.Abs( exp ).ToString( CultureInfo.InvariantCulture );
}
public string TypeOf()
{
switch ( Kind )
{
case ValueKind.Undefined: return "undefined";
case ValueKind.Null: return "null";
case ValueKind.Bool: return "boolean";
case ValueKind.Number: return "number";
case ValueKind.String: return "string";
default:
if ( Obj is DisplayObject d ) return d is EditText ? "object" : "movieclip";
if ( Obj is AsFunction ) return "function";
return "object";
}
}
public override string ToString() => ToStr( null );
public bool SameRef( Value o ) => Kind == o.Kind && (Kind switch
{
ValueKind.Number => Num == o.Num,
ValueKind.Bool => Num == o.Num,
ValueKind.String => Str == o.Str,
ValueKind.Object => ReferenceEquals( Obj, o.Obj ),
_ => true
});
}