diff --git a/src/Lumina/Text/Expressions/BaseExpression.cs b/src/Lumina/Text/Expressions/BaseExpression.cs
index 6b372238..22a0d172 100644
--- a/src/Lumina/Text/Expressions/BaseExpression.cs
+++ b/src/Lumina/Text/Expressions/BaseExpression.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
namespace Lumina.Text.Expressions;
@@ -24,6 +25,18 @@ public abstract class BaseExpression
/// Target to write this expression to.
public abstract void Encode( Stream stream );
+ /// Represent this expression as a part of macro string.
+ /// Target string builder.
+ public abstract void AppendMacroStringToStringBuilder( StringBuilder sb );
+
+ ///
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ AppendMacroStringToStringBuilder( sb );
+ return sb.ToString();
+ }
+
///
/// Parse given Stream into an Expression.
///
diff --git a/src/Lumina/Text/Expressions/BinaryExpression.cs b/src/Lumina/Text/Expressions/BinaryExpression.cs
index cff78f50..cd50d23d 100644
--- a/src/Lumina/Text/Expressions/BinaryExpression.cs
+++ b/src/Lumina/Text/Expressions/BinaryExpression.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
namespace Lumina.Text.Expressions;
@@ -37,18 +38,37 @@ public BinaryExpression( ExpressionType typeByte, BaseExpression operand1, BaseE
public override ExpressionType ExpressionType { get; }
///
- public override string ToString()
+ public override void AppendMacroStringToStringBuilder( StringBuilder sb )
{
- return ExpressionType switch
+ sb.Append( '[' );
+ Operand1.AppendMacroStringToStringBuilder( sb );
+
+ switch( ExpressionType )
{
- ExpressionType.GreaterThanOrEqualTo => $"[{Operand1}>={Operand2}]",
- ExpressionType.GreaterThan => $"[{Operand1}>{Operand2}]",
- ExpressionType.LessThanOrEqualTo => $"[{Operand1}<={Operand2}]",
- ExpressionType.LessThan => $"[{Operand1}<{Operand2}]",
- ExpressionType.Equal => $"[{Operand1}=={Operand2}]",
- ExpressionType.NotEqual => $"[{Operand1}!={Operand2}]",
- _ => throw new NotImplementedException() // cannot reach, as this instance is immutable and this field is filtered from constructor
- };
+ case ExpressionType.GreaterThanOrEqualTo:
+ sb.Append( ">=" );
+ break;
+ case ExpressionType.GreaterThan:
+ sb.Append( '>' );
+ break;
+ case ExpressionType.LessThanOrEqualTo:
+ sb.Append( "<=" );
+ break;
+ case ExpressionType.LessThan:
+ sb.Append( '<' );
+ break;
+ case ExpressionType.Equal:
+ sb.Append( "==" );
+ break;
+ case ExpressionType.NotEqual:
+ sb.Append( "!=" );
+ break;
+ default:
+ throw new NotSupportedException(); // cannot reach, as this instance is immutable and this field is filtered from constructor
+ }
+
+ Operand2.AppendMacroStringToStringBuilder( sb );
+ sb.Append( ']' );
}
///
diff --git a/src/Lumina/Text/Expressions/IntegerExpression.cs b/src/Lumina/Text/Expressions/IntegerExpression.cs
index 7f58b135..2bbb813d 100644
--- a/src/Lumina/Text/Expressions/IntegerExpression.cs
+++ b/src/Lumina/Text/Expressions/IntegerExpression.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
namespace Lumina.Text.Expressions;
@@ -94,6 +95,9 @@ public static int CalculateSize( uint value )
///
public override string ToString() => ( (int)Value ).ToString();
+ ///
+ public override void AppendMacroStringToStringBuilder( StringBuilder sb ) => sb.Append( (int) Value );
+
///
/// Parse given Stream into an IntegerExpression.
///
diff --git a/src/Lumina/Text/Expressions/ParameterExpression.cs b/src/Lumina/Text/Expressions/ParameterExpression.cs
index c58c2b64..86efa296 100644
--- a/src/Lumina/Text/Expressions/ParameterExpression.cs
+++ b/src/Lumina/Text/Expressions/ParameterExpression.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
namespace Lumina.Text.Expressions;
@@ -31,16 +32,17 @@ public ParameterExpression( ExpressionType typeByte, BaseExpression operand )
public override ExpressionType ExpressionType { get; }
///
- public override string ToString()
+ public override void AppendMacroStringToStringBuilder( StringBuilder sb )
{
- return ExpressionType switch
+ sb.Append( ExpressionType switch
{
- ExpressionType.IntegerParameter => $"lnum{Operand}",
- ExpressionType.PlayerParameter => $"gnum{Operand}",
- ExpressionType.StringParameter => $"lstr{Operand}",
- ExpressionType.ObjectParameter => $"gstr{Operand}",
- _ => throw new NotImplementedException() // cannot reach, as this instance is immutable and this field is filtered from constructor
- };
+ ExpressionType.LocalNumber => "lnum",
+ ExpressionType.GlobalNumber => "gnum",
+ ExpressionType.LocalString => "lstr",
+ ExpressionType.GlobalString => "gstr",
+ _ => throw new NotSupportedException() // cannot reach, as this instance is immutable and this field is filtered from constructor
+ } );
+ Operand.AppendMacroStringToStringBuilder( sb );
}
///
diff --git a/src/Lumina/Text/Expressions/PlaceholderExpression.cs b/src/Lumina/Text/Expressions/PlaceholderExpression.cs
index 0694c5b9..c2067bbd 100644
--- a/src/Lumina/Text/Expressions/PlaceholderExpression.cs
+++ b/src/Lumina/Text/Expressions/PlaceholderExpression.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
namespace Lumina.Text.Expressions;
@@ -28,9 +29,9 @@ public PlaceholderExpression( ExpressionType expressionType )
public override void Encode( Stream stream ) => stream.WriteByte( (byte)ExpressionType );
///
- public override string ToString()
+ public override void AppendMacroStringToStringBuilder( StringBuilder sb )
{
- return ExpressionType switch
+ sb.Append( ExpressionType switch
{
ExpressionType.Millisecond => "t_msec",
ExpressionType.Second => "t_sec",
@@ -41,8 +42,8 @@ public override string ToString()
ExpressionType.Month => "t_mon",
ExpressionType.Year => "t_year",
ExpressionType.StackColor => "stackcolor",
- _ => $"Placeholder#{(byte)ExpressionType:X02}"
- };
+ _ => $"Placeholder#{(byte) ExpressionType:X02}"
+ } );
}
///
diff --git a/src/Lumina/Text/Expressions/StringExpression.cs b/src/Lumina/Text/Expressions/StringExpression.cs
index e71e389a..42b3b608 100644
--- a/src/Lumina/Text/Expressions/StringExpression.cs
+++ b/src/Lumina/Text/Expressions/StringExpression.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
+using System.Text;
namespace Lumina.Text.Expressions;
@@ -45,7 +46,8 @@ public override void Encode( Stream stream )
}
///
- public override string ToString() => Value?.ToString() ?? string.Empty;
+ public override void AppendMacroStringToStringBuilder( StringBuilder sb ) =>
+ Value.AppendMacroStringToStringBuilder( sb, true );
///
/// Parse given Stream into a StringExpression.
diff --git a/src/Lumina/Text/Payloads/BasePayload.cs b/src/Lumina/Text/Payloads/BasePayload.cs
index a0b219b5..5eb67afb 100644
--- a/src/Lumina/Text/Payloads/BasePayload.cs
+++ b/src/Lumina/Text/Payloads/BasePayload.cs
@@ -256,5 +256,45 @@ public override string ToString()
ex => ex is StringExpression se ? se.Value.ToMacroString() : ex.ToString()
) )})>";
}
+
+ internal void AppendMacroStringToStringBuilder( StringBuilder sb, bool forStringExpression )
+ {
+ if( PayloadType == PayloadType.Text )
+ {
+ foreach( var c in RawString )
+ {
+ switch( forStringExpression )
+ {
+ case true when c is '<' or '>' or '[' or ']' or '(' or ')' or ',' or '\\':
+ case false when c is '<' or '\\':
+ sb.Append( '\\' );
+ break;
+ }
+
+ sb.Append( c );
+ }
+
+ return;
+ }
+
+ sb.Append( $"<{( (MacroCode) PayloadType ).GetEncodeName()}" );
+ if( Expressions.Count > 0 )
+ {
+ sb.Append( '(' );
+ Expressions[ 0 ].AppendMacroStringToStringBuilder( sb );
+ if( Expressions.Count > 1 )
+ {
+ for( var i = 1; i < Expressions.Count; i++ )
+ {
+ sb.Append( ',' );
+ Expressions[ i ].AppendMacroStringToStringBuilder( sb );
+ }
+ }
+
+ sb.Append( ')' );
+ }
+
+ sb.Append( '>' );
+ }
}
}
\ No newline at end of file
diff --git a/src/Lumina/Text/ReadOnly/ReadOnlySeExpressionSpan.cs b/src/Lumina/Text/ReadOnly/ReadOnlySeExpressionSpan.cs
index 088dc64c..65c31a49 100644
--- a/src/Lumina/Text/ReadOnly/ReadOnlySeExpressionSpan.cs
+++ b/src/Lumina/Text/ReadOnly/ReadOnlySeExpressionSpan.cs
@@ -179,43 +179,90 @@ public override int GetHashCode()
///
public override string ToString()
+ {
+ var sb = new StringBuilder();
+ AppendMacroStringToStringBuilder( sb );
+ return sb.ToString();
+ }
+
+ /// Writes the encodeable macro representation of this instance of to the given string builder.
+ /// Target string builder.
+ /// The encodeable macro representation.
+ public void AppendMacroStringToStringBuilder( StringBuilder sb )
{
if( Body.IsEmpty )
- return "(?)";
+ {
+ sb.Append( "" );
+ return;
+ }
if( TryGetUInt( out var u32 ) )
- return u32.ToString();
+ {
+ sb.Append( u32 );
+ return;
+ }
if( TryGetString( out var s ) )
- return $"\"{s.ToString().Replace( "\\", "\\\\" ).Replace( "\"", "\\\"" )}\"";
+ {
+ s.AppendMacroStringToStringBuilder( sb, true );
+ return;
+ }
if( TryGetPlaceholderExpression( out var exprType ) )
{
if( ( (ExpressionType) exprType ).GetNativeName() is { } nativeName )
- return nativeName;
- return $"?x{exprType:X02}";
+ sb.Append( nativeName );
+ else
+ sb.Append( $"" );
+ return;
}
if( TryGetParameterExpression( out exprType, out var e1 ) )
{
- if( ( (ExpressionType) exprType ).GetNativeName() is { } nativeName )
- return $"{nativeName}({e1.ToString()})";
- throw new InvalidOperationException( "All native names must be defined for unary expressions." );
+ if( ( (ExpressionType) exprType ).GetNativeName() is not { } nativeName )
+ throw new InvalidOperationException( "All native names must be defined for unary expressions." );
+ sb.Append( nativeName );
+ e1.AppendMacroStringToStringBuilder( sb );
+ return;
}
if( TryGetBinaryExpression( out exprType, out e1, out var e2 ) )
{
- if( ( (ExpressionType) exprType ).GetNativeName() is { } nativeName )
- return $"{e1.ToString()} {nativeName} {e2.ToString()}";
- throw new InvalidOperationException( "All native names must be defined for binary expressions." );
+ sb.Append( '[' );
+ e1.AppendMacroStringToStringBuilder( sb );
+ switch( (ExpressionType)exprType )
+ {
+ case ExpressionType.GreaterThanOrEqualTo:
+ sb.Append( ">=" );
+ break;
+ case ExpressionType.GreaterThan:
+ sb.Append( '>' );
+ break;
+ case ExpressionType.LessThanOrEqualTo:
+ sb.Append( "<=" );
+ break;
+ case ExpressionType.LessThan:
+ sb.Append( '<' );
+ break;
+ case ExpressionType.Equal:
+ sb.Append( "==" );
+ break;
+ case ExpressionType.NotEqual:
+ sb.Append( "!=" );
+ break;
+ default:
+ throw new NotSupportedException("should not happen");
+ }
+
+ e2.AppendMacroStringToStringBuilder( sb );
+ sb.Append( ']' );
+ return;
}
- var sb = new StringBuilder();
- sb.EnsureCapacity( 1 + 3 * Body.Length );
- sb.Append( $"({Body[ 0 ]:X02}" );
- for( var i = 1; i < Body.Length; i++ )
- sb.Append( $" {Body[ i ]:X02}" );
- sb.Append( ')' );
- return sb.ToString();
+ sb.EnsureCapacity( sb.Length + 7 + 3 * Body.Length );
+ sb.Append( "' );
}
}
\ No newline at end of file
diff --git a/src/Lumina/Text/ReadOnly/ReadOnlySePayloadSpan.cs b/src/Lumina/Text/ReadOnly/ReadOnlySePayloadSpan.cs
index 4a016952..ae6a3a34 100644
--- a/src/Lumina/Text/ReadOnly/ReadOnlySePayloadSpan.cs
+++ b/src/Lumina/Text/ReadOnly/ReadOnlySePayloadSpan.cs
@@ -420,46 +420,81 @@ public override int GetHashCode()
///
public override string ToString()
+ {
+ var sb = new StringBuilder();
+ AppendMacroStringToStringBuilder( sb, false );
+ return sb.ToString();
+ }
+
+ /// Writes the encodeable macro representation of this instance of to the given string builder.
+ /// Target string builder.
+ /// Whether this is being encoded to be used as a string expression.
+ /// The encodeable macro representation.
+ public void AppendMacroStringToStringBuilder( StringBuilder sb, bool forStringExpression )
{
switch( Type )
{
case ReadOnlySePayloadType.Text:
- return Encoding.UTF8.GetString( Body ).Replace( "<", "\\<" );
+ {
+ var remaining = Body;
+ Span< char > buf = stackalloc char[2];
+ while( !remaining.IsEmpty )
+ {
+ Rune.DecodeFromUtf8( remaining, out var rune, out var consumed );
+ switch( forStringExpression )
+ {
+ case true when rune.Value is '<' or '>' or '[' or ']' or '(' or ')' or ',' or '\\':
+ case false when rune.Value is '<' or '\\':
+ sb.Append( '\\' );
+ break;
+ }
+
+ sb.Append( buf[ ..rune.EncodeToUtf16( buf ) ] );
+ remaining = remaining[ consumed.. ];
+ }
+
+ break;
+ }
case ReadOnlySePayloadType.Macro:
{
- var sb = new StringBuilder( "<" );
+ sb.Append( '<' );
// Not using ternary operator here, as it's better to let StringBuilder handle the string interpolation.
if( MacroCode.GetEncodeName() is { } encodeName )
sb.Append( encodeName );
else
- sb.Append( $"x{(uint) MacroCode:X02}" );
+ sb.Append( $"payload:{(uint) MacroCode:X02}" );
var expre = GetEnumerator();
if( expre.MoveNext() )
{
sb.Append( '(' );
- sb.Append( expre.Current.ToString() );
+ expre.Current.AppendMacroStringToStringBuilder( sb );
while( expre.MoveNext() )
- sb.Append( ", " ).Append( expre.Current.ToString() );
+ {
+ sb.Append( ',' );
+ expre.Current.AppendMacroStringToStringBuilder( sb );
+ }
+
sb.Append( ')' );
}
- return sb.Append( '>' ).ToString();
+ sb.Append( '>' );
+ break;
}
case var _ when Body.Length == 0:
- return ">";
+ sb.Append( "" );
+ break;
default:
- {
- var sb = new StringBuilder( 3 * Body.Length + 3 );
- sb.Append( "" );
- foreach (var b in Body)
+ sb.EnsureCapacity( sb.Length + 10 + 3 * Body.Length );
+ sb.Append( "' ).ToString();
- }
+ sb.Append( '>' );
+ break;
}
}
diff --git a/src/Lumina/Text/ReadOnly/ReadOnlySeStringSpan.cs b/src/Lumina/Text/ReadOnly/ReadOnlySeStringSpan.cs
index 461d24ac..aceb533d 100644
--- a/src/Lumina/Text/ReadOnly/ReadOnlySeStringSpan.cs
+++ b/src/Lumina/Text/ReadOnly/ReadOnlySeStringSpan.cs
@@ -304,11 +304,20 @@ public override int GetHashCode()
public override string ToString()
{
var sb = new StringBuilder();
- foreach( var v in this )
- sb.Append( v.ToString() );
+ AppendMacroStringToStringBuilder( sb, false );
return sb.ToString();
}
+ /// Writes the encodeable macro representation of this instance of to the given string builder.
+ /// Target string builder.
+ /// Whether this is being encoded to be used as a string expression.
+ /// The encodeable macro representation.
+ public void AppendMacroStringToStringBuilder( StringBuilder sb, bool forStringExpression )
+ {
+ foreach( var v in this )
+ v.AppendMacroStringToStringBuilder( sb, forStringExpression );
+ }
+
///
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public Enumerator GetEnumerator() => new( new( this ) );
diff --git a/src/Lumina/Text/SeString.cs b/src/Lumina/Text/SeString.cs
index 76d947c7..51c94e8e 100644
--- a/src/Lumina/Text/SeString.cs
+++ b/src/Lumina/Text/SeString.cs
@@ -187,5 +187,11 @@ public string ToMacroString()
{
return string.Concat( Payloads );
}
+
+ internal void AppendMacroStringToStringBuilder( StringBuilder sb, bool forStringExpression )
+ {
+ foreach( var p in Payloads )
+ p.AppendMacroStringToStringBuilder( sb, forStringExpression );
+ }
}
}
\ No newline at end of file