Skip to content

Commit

Permalink
Add support for defining custom JsString deriving type
Browse files Browse the repository at this point in the history
* move ToBoolean as virtual method to JsValue
  • Loading branch information
lahma committed Jan 14, 2023
1 parent 97358eb commit b15ce75
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 171 deletions.
88 changes: 88 additions & 0 deletions Jint.Tests.PublicInterface/RavenApiUsageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,92 @@ private static void TestArrayAccess(Engine engine, JsArray array, string name)
Assert.Equal(106L, array.Length);
Assert.True(array.All(x => x is JsNumber or JsUndefined or JsNumber or JsString or JsBoolean));
}

// Checks different ways how string can be checked for equality without the need to materialize lazy value
[Fact]
public void CanInheritCustomString()
{
var engine = new Engine();

var str = new CustomString("the-value");
engine.SetValue("str", str);

var empty = new CustomString("");
engine.SetValue("empty", empty);

var obj = new JsObject(engine);
obj.Set("name", new CustomString("the name"));
engine.SetValue("obj", obj);

var array = new JsArray(engine, Enumerable.Range(1, 100).Select(x => new CustomString(x.ToString())).ToArray<JsValue>());
engine.SetValue("array", array);

Assert.True(engine.Evaluate("str ? true : false").AsBoolean());
Assert.False(engine.Evaluate("empty ? true : false").AsBoolean());

Assert.True(engine.Evaluate("array.includes('2')").AsBoolean());
Assert.True(engine.Evaluate("array.filter(x => x === '2').length > 0").AsBoolean());

engine.SetValue("objArray", new JsArray(engine, new JsValue[] { obj, obj }));
Assert.True(engine.Evaluate("objArray.filter(x => x.name === 'the name').length === 2").AsBoolean());

Assert.Equal(9, engine.Evaluate("str.length"));

Assert.True(engine.Evaluate("str == 'the-value'").AsBoolean());
Assert.True(engine.Evaluate("str === 'the-value'").AsBoolean());

Assert.True(engine.Evaluate("str.indexOf('value-too-long') === -1").AsBoolean());
Assert.True(engine.Evaluate("str.lastIndexOf('value-too-long') === -1").AsBoolean());
Assert.False(engine.Evaluate("str.startsWith('value-too-long')").AsBoolean());
Assert.False(engine.Evaluate("str.endsWith('value-too-long')").AsBoolean());
Assert.False(engine.Evaluate("str.includes('value-too-long')").AsBoolean());

Assert.True(engine.Evaluate("empty.trim() === ''").AsBoolean());
Assert.True(engine.Evaluate("empty.trimStart() === ''").AsBoolean());
Assert.True(engine.Evaluate("empty.trimEnd() === ''").AsBoolean());
}

private sealed class CustomString : JsString
{
private readonly string _value;

public CustomString(string value) : base(null)
{
_value = value;
}

public override string ToString()
{
// when called we know that we couldn't use fast paths
throw new InvalidOperationException("I don't want to be materialized!");
}

public override char this[int index] => _value[index];

public override int Length => _value.Length;

public override bool Equals(JsString obj)
{
return obj switch
{
CustomString customString => _value == customString._value,
_ => _value == obj.ToString()
};
}

public override bool IsLooselyEqual(JsValue value)
{
return value switch
{
CustomString customString => _value == customString._value,
JsString jsString => _value == jsString.ToString(),
_ => base.IsLooselyEqual(value)
};
}

public override int GetHashCode()
{
return _value.GetHashCode();
}
}
}
2 changes: 1 addition & 1 deletion Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public Engine SetValue(string name, Delegate value)

public Engine SetValue(string name, string value)
{
return SetValue(name, new JsString(value));
return SetValue(name, JsString.Create(value));
}

public Engine SetValue(string name, double value)
Expand Down
9 changes: 4 additions & 5 deletions Jint/Native/JsBigInt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,13 @@ internal static JsBigInt Create(JsValue value)
}


public override object ToObject()
{
return _value;
}
public override object ToObject() => _value;

internal override bool ToBoolean() => _value != 0;

public static bool operator ==(JsBigInt a, double b)
{
return a is not null && TypeConverter.IsIntegralNumber(b) && a._value == (long) b;
return TypeConverter.IsIntegralNumber(b) && a._value == (long) b;
}

public static bool operator !=(JsBigInt a, double b)
Expand Down
7 changes: 3 additions & 4 deletions Jint/Native/JsBoolean.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ private JsBoolean(bool value) : base(Types.Boolean)

internal static JsBoolean Create(bool value) => value ? True : False;

public override object ToObject()
{
return _value ? BoxedTrue : BoxedFalse;
}
public override object ToObject() => _value ? BoxedTrue : BoxedFalse;

internal override bool ToBoolean() => _value;

public override string ToString()
{
Expand Down
12 changes: 4 additions & 8 deletions Jint/Native/JsNull.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@ internal JsNull() : base(Types.Null)
{
}

public override object ToObject()
{
return null!;
}
public override object ToObject() => null!;

public override string ToString()
{
return "null";
}
internal override bool ToBoolean() => false;

public override string ToString() => "null";

public override bool IsLooselyEqual(JsValue value)
{
Expand Down
16 changes: 9 additions & 7 deletions Jint/Native/JsNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,16 @@ public JsNumber(long value) : base(value is <= int.MaxValue and >= int.MinValue
_value = value;
}

public override object ToObject()
public override object ToObject() => _value;

internal override bool ToBoolean()
{
return _value;
if (_type == InternalTypes.Integer)
{
return (int) _value != 0;
}

return _value != 0 && !double.IsNaN(_value);
}

internal static JsNumber Create(object value)
Expand Down Expand Up @@ -260,11 +267,6 @@ public override bool Equals(JsValue? obj)
return Equals(obj as JsNumber);
}

public override bool Equals(object? obj)
{
return Equals(obj as JsNumber);
}

public bool Equals(JsNumber? other)
{
if (other is null)
Expand Down
113 changes: 67 additions & 46 deletions Jint/Native/JsString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,26 @@ public JsString(char value) : base(Types.String)
_value = value.ToString();
}

public override object ToObject()
public static bool operator ==(JsString? a, JsString? b)
{
return _value;
if (a is not null)
{
return a.Equals(b);
}

if (a is null)
{
return b is null;
}

return b is not null && a.Equals(b);
}

public static bool operator ==(JsValue? a, JsString? b)
{
if (a is JsString s && b is not null)
{
return s.ToString() == b.ToString();
return s.Equals(b);
}

if (a is null)
Expand All @@ -84,9 +94,9 @@ public override object ToObject()

public static bool operator ==(JsString? a, JsValue? b)
{
if (a is not null && b is JsString s)
if (a is not null)
{
return s.ToString() == b.ToString();
return a.Equals(b);
}

if (a is null)
Expand All @@ -107,25 +117,11 @@ public override object ToObject()
return !(a == b);
}

public virtual char this[int index] => _value[index];

public virtual JsString Append(JsValue jsValue)
public static bool operator !=(JsString a, JsString b)
{
return new ConcatenatedString(string.Concat(_value, TypeConverter.ToString(jsValue)));
}

internal virtual JsString EnsureCapacity(int capacity)
{
return new ConcatenatedString(_value, capacity);
}

internal virtual bool IsNullOrEmpty()
{
return string.IsNullOrEmpty(_value);
return !(a == b);
}

public virtual int Length => _value.Length;

internal static JsString Create(string value)
{
if (value.Length > 1)
Expand Down Expand Up @@ -191,21 +187,59 @@ internal static JsValue Create(ulong value)
return new JsString(TypeConverter.ToString(value));
}

public override string ToString()

public virtual char this[int index] => _value[index];

public virtual int Length => _value.Length;

internal virtual JsString Append(JsValue jsValue)
{
return new ConcatenatedString(string.Concat(ToString(), TypeConverter.ToString(jsValue)));
}

internal virtual JsString EnsureCapacity(int capacity)
{
return new ConcatenatedString(_value, capacity);
}

public sealed override object ToObject() => ToString();

internal sealed override bool ToBoolean()
{
return _value;
return Length > 0;
}

internal int IndexOf(string value, StringComparison comparisonType)
public override string ToString() => _value;

internal int IndexOf(string value, int startIndex = 0)
{
return ToString().IndexOf(value, comparisonType);
if (Length - startIndex < value.Length)
{
return -1;
}
return ToString().IndexOf(value, startIndex, StringComparison.Ordinal);
}

internal int IndexOf(char value)
{
if (Length == 0)
{
return -1;
}
return ToString().IndexOf(value);
}

internal bool StartsWith(string value, int start = 0)
{
return value.Length + start <= Length && ToString().AsSpan(start).StartsWith(value.AsSpan());
}

internal bool EndsWith(string value, int end = 0)
{
var start = end - value.Length;
return start >= 0 && ToString().AsSpan(start, value.Length).EndsWith(value.AsSpan());
}

internal string Substring(int startIndex, int length)
{
return ToString().Substring(startIndex, length);
Expand All @@ -216,12 +250,12 @@ internal string Substring(int startIndex)
return ToString().Substring(startIndex);
}

public override bool Equals(JsValue? obj)
public sealed override bool Equals(JsValue? obj)
{
return Equals(obj as JsString);
}

public bool Equals(JsString? other)
public virtual bool Equals(JsString? other)
{
if (other is null)
{
Expand Down Expand Up @@ -251,7 +285,7 @@ public override bool IsLooselyEqual(JsValue value)
return base.IsLooselyEqual(value);
}

public override bool Equals(object obj)
public sealed override bool Equals(object obj)
{
return Equals(obj as JsString);
}
Expand Down Expand Up @@ -292,7 +326,7 @@ public override string ToString()

public override char this[int index] => _stringBuilder?[index] ?? _value[index];

public override JsString Append(JsValue jsValue)
internal override JsString Append(JsValue jsValue)
{
var value = TypeConverter.ToString(jsValue);
if (_stringBuilder == null)
Expand All @@ -312,17 +346,9 @@ internal override JsString EnsureCapacity(int capacity)
return this;
}

internal override bool IsNullOrEmpty()
{
return _stringBuilder == null && string.IsNullOrEmpty(_value)
|| _stringBuilder != null && _stringBuilder.Length == 0;
}

public override int Length => _stringBuilder?.Length ?? _value?.Length ?? 0;

public override object ToObject() => ToString();

public override bool Equals(JsValue? other)
public override bool Equals(JsString? other)
{
if (other is ConcatenatedString cs)
{
Expand All @@ -346,17 +372,12 @@ public override bool Equals(JsValue? other)
return ToString() == cs.ToString();
}

if (other is JsString jsString)
if (other is null || other.Length != Length)
{
if (jsString._value.Length != Length)
{
return false;
}

return ToString() == jsString._value;
return false;
}

return base.Equals(other);
return ToString() == other.ToString();
}

public override int GetHashCode()
Expand Down

0 comments on commit b15ce75

Please sign in to comment.