diff --git a/src/Core/Rexl.Bind/Operations/BuiltinFunctions.cs b/src/Core/Rexl.Bind/Operations/BuiltinFunctions.cs
index ef089a2..2fdd4d2 100644
--- a/src/Core/Rexl.Bind/Operations/BuiltinFunctions.cs
+++ b/src/Core/Rexl.Bind/Operations/BuiltinFunctions.cs
@@ -378,6 +378,11 @@ private BuiltinFunctions()
AddOne(TextTrimFunc.TrimStart, new Sig(S.AboutTrimStart, A.Create(S.ArgSource, S.AboutTrimStart_Source)));
AddOne(TextTrimFunc.TrimEnd, new Sig(S.AboutTrimEnd, A.Create(S.ArgSource, S.AboutTrimEnd_Source)));
+ AddOne(TextPadFunc.PadCenter, new Sig(S.AboutPadCenter, A.Create(S.ArgSource, S.AboutPad_Source), A.Create(S.ArgLength, S.AboutPad_Len)));
+ AddOne(TextPadFunc.PadCenter, "PadCenter");
+ AddOne(TextPadFunc.PadStart, new Sig(S.AboutPadStart, A.Create(S.ArgSource, S.AboutPad_Source), A.Create(S.ArgLength, S.AboutPad_Len)));
+ AddOne(TextPadFunc.PadEnd, new Sig(S.AboutPadEnd, A.Create(S.ArgSource, S.AboutPad_Source), A.Create(S.ArgLength, S.AboutPad_Len)));
+
AddOne(TextReplaceFunc.Instance, new Sig(S.AboutTextReplace,
A.Create(S.ArgSource, S.AboutTextReplace_Source),
A.Create(S.TextReplace_Remove, S.AboutTextReplace_Remove),
diff --git a/src/Core/Rexl.Bind/Operations/Funcs/Text.cs b/src/Core/Rexl.Bind/Operations/Funcs/Text.cs
index e169e6d..52e5fd2 100644
--- a/src/Core/Rexl.Bind/Operations/Funcs/Text.cs
+++ b/src/Core/Rexl.Bind/Operations/Funcs/Text.cs
@@ -758,3 +758,141 @@ public static string Exec(string src, string remove, string insert)
return src.Replace(remove, insert);
}
}
+
+///
+/// Functions to add padding to either end of a string.
+/// The center version (PadCenter) will add spaces to both ends of the given string.
+/// The start version (PadStart) will add spaces to the start of the given string.
+/// The end version (PadEnd) will add spaces to the end of the given string.
+///
+public sealed partial class TextPadFunc : RexlOper
+{
+ public enum PadKind : byte
+ {
+ // Center justification, pad both ends of the string
+ Center,
+ // Pad the start of the string.
+ Start,
+ // Pad the end of the string.
+ End
+ }
+
+ public static readonly TextPadFunc PadCenter = new TextPadFunc(PadKind.Center, "Pad");
+ public static readonly TextPadFunc PadStart = new TextPadFunc(PadKind.Start, "PadStart");
+ public static readonly TextPadFunc PadEnd = new TextPadFunc(PadKind.End, "PadEnd");
+
+ public PadKind Kind { get; }
+
+ public Func Map { get; }
+
+ private TextPadFunc(PadKind kind, string name)
+ : base(isFunc: true, new DName(name), BindUtil.TextNs, 2, 2)
+ {
+ switch (kind)
+ {
+ case PadKind.Start:
+ Map = ExecStart;
+ break;
+ case PadKind.End:
+ Map = ExecEnd;
+ break;
+ default:
+ Validation.Assert(kind == PadKind.Center);
+ Map = ExecCenter;
+ break;
+ }
+
+ Kind = kind;
+ }
+
+ protected override ArgTraits GetArgTraitsCore(int carg)
+ {
+ Validation.Assert(SupportsArity(carg));
+ var maskAll = BitSet.GetMask(carg);
+ var maskOpt = maskAll.ClearBit(0);
+ return ArgTraitsLifting.Create(this, carg, maskLiftSeq: maskAll, maskLiftTen: maskAll, maskLiftOpt: maskOpt);
+ }
+
+ protected override (DType, Immutable.Array) SpecializeTypesCore(InvocationInfo info)
+ {
+ Validation.AssertValue(info);
+ Validation.Assert(SupportsArity(info.Arity));
+ Validation.Assert(info.Arity == 2);
+
+ return (DType.Text, Immutable.Array.Create(DType.Text, DType.I8Req));
+ }
+
+ protected override bool CertifyCore(BndCallNode call, ref bool full)
+ {
+ if (call.Type != DType.Text)
+ return false;
+ var args = call.Args;
+ if (args[0].Type != DType.Text)
+ return false;
+ if (args[1].Type != DType.I8Req)
+ return false;
+ return true;
+ }
+
+ protected override BoundNode ReduceCore(IReducer reducer, BndCallNode call)
+ {
+ Validation.AssertValue(reducer);
+ Validation.Assert(IsValidCall(call));
+
+ var lenArg = call.Args[1];
+ if (lenArg.TryGetI8(out var len))
+ {
+ var srcArg = call.Args[0];
+ if (len <= 0)
+ return srcArg;
+ if (srcArg.TryGetString(out var str))
+ {
+ if (Util.Size(str) >= len)
+ return srcArg;
+ return BndStrNode.Create(Map(str, len));
+ }
+ }
+
+ return call;
+ }
+
+ public static string ExecCenter(string src, long len)
+ {
+ if (len <= 0)
+ return src;
+ int count = (int)Math.Min(len, int.MaxValue);
+ if (string.IsNullOrEmpty(src))
+ return new string(' ', count);
+ if (count <= src.Length)
+ return src;
+ return string.Create(count, src.AsMemory(), static (dst, mem) =>
+ {
+ int spaces = (dst.Length - mem.Length) / 2;
+
+ if (spaces > 0)
+ dst.Slice(0, spaces).Fill(' ');
+ mem.Span.CopyTo(dst.Slice(spaces, mem.Length));
+ dst.Slice(spaces + mem.Length).Fill(' ');
+ });
+ }
+
+ public static string ExecStart(string src, long len)
+ {
+ if (len <= 0)
+ return src;
+ int count = (int)Math.Min(len, int.MaxValue);
+ if (string.IsNullOrEmpty(src))
+ return new string(' ', count);
+ return src.PadLeft(count);
+ }
+
+ public static string ExecEnd(string src, long len)
+ {
+ if (len <= 0)
+ return src;
+ int count = (int)Math.Min(len, int.MaxValue);
+ if (string.IsNullOrEmpty(src))
+ return new string(' ', count);
+ return src.PadRight(count);
+ }
+}
diff --git a/src/Core/Rexl.Bind/Strings/RexlStrings.cs b/src/Core/Rexl.Bind/Strings/RexlStrings.cs
index bf86c7b..299536a 100644
--- a/src/Core/Rexl.Bind/Strings/RexlStrings.cs
+++ b/src/Core/Rexl.Bind/Strings/RexlStrings.cs
@@ -29,6 +29,7 @@ static partial class RexlStrings
public static readonly StringId ArgX = new(nameof(ArgX), "x");
public static readonly StringId ArgY = new(nameof(ArgY), "y");
public static readonly StringId ArgAngle = new(nameof(ArgAngle), "angle");
+ public static readonly StringId ArgLength = new(nameof(ArgLength), "length");
public static readonly StringId AboutArgSeqSource = new(nameof(AboutArgSeqSource), "The source sequence.");
public static readonly StringId AboutArgPredicate = new(nameof(AboutArgPredicate), "The condition with which to test an item.");
@@ -342,6 +343,12 @@ static partial class RexlStrings
public static readonly StringId AboutUpper = new(nameof(AboutUpper), "Converts text to uppercase.");
public static readonly StringId AboutUpper_Source = new(nameof(AboutUpper_Source), "The text to convert to uppercase.");
+ public static readonly StringId AboutPadCenter = new(nameof(AboutPadCenter), "Adds spaces to both ends of the given text so the result is of the given length.");
+ public static readonly StringId AboutPadStart = new(nameof(AboutPadStart), "Adds spaces to the start of the given text so the result is of the given length.");
+ public static readonly StringId AboutPadEnd = new(nameof(AboutPadEnd), "Adds spaces to the end of the given text so the result is of the given length.");
+ public static readonly StringId AboutPad_Source = new(nameof(AboutPad_Source), "The text to add padding to.");
+ public static readonly StringId AboutPad_Len = new(nameof(AboutPad_Len), "The minimum length of the text after padding.");
+
public static readonly StringId AboutStartsWith = new(nameof(AboutStartsWith), "Tests whether the beginning of the source text matches the lookup text.");
public static readonly StringId AboutStartsWith_Source = new(nameof(AboutStartsWith_Source), "The text to look in.");
public static readonly StringId AboutStartsWith_LookUp = new(nameof(AboutStartsWith_LookUp), "The text to look for.");
diff --git a/src/Core/Rexl.Bind/Strings/RexlStrings.tt b/src/Core/Rexl.Bind/Strings/RexlStrings.tt
index 84c5af0..488b9da 100644
--- a/src/Core/Rexl.Bind/Strings/RexlStrings.tt
+++ b/src/Core/Rexl.Bind/Strings/RexlStrings.tt
@@ -23,6 +23,7 @@ ArgValue = "value"
ArgX = "x"
ArgY = "y"
ArgAngle = "angle"
+ArgLength = "length"
AboutArgSeqSource = "The source sequence."
AboutArgPredicate = "The condition with which to test an item."
@@ -336,6 +337,12 @@ AboutLower_Source = "The text to convert to lowercase."
AboutUpper = "Converts text to uppercase."
AboutUpper_Source = "The text to convert to uppercase."
+AboutPadCenter = "Adds spaces to both ends of the given text so the result is of the given length."
+AboutPadStart = "Adds spaces to the start of the given text so the result is of the given length."
+AboutPadEnd = "Adds spaces to the end of the given text so the result is of the given length."
+AboutPad_Source = "The text to add padding to."
+AboutPad_Len = "The minimum length of the text after padding."
+
AboutStartsWith = "Tests whether the beginning of the source text matches the lookup text."
AboutStartsWith_Source = "The text to look in."
AboutStartsWith_LookUp = "The text to look for."
diff --git a/src/Core/Rexl.Code/Operations/BuiltinGenerators.cs b/src/Core/Rexl.Code/Operations/BuiltinGenerators.cs
index 8f3b13e..e3658f8 100644
--- a/src/Core/Rexl.Code/Operations/BuiltinGenerators.cs
+++ b/src/Core/Rexl.Code/Operations/BuiltinGenerators.cs
@@ -59,6 +59,7 @@ private BuiltinGenerators()
Add(TextPartGen.Instance);
Add(TextTrimGen.Instance);
Add(TextReplaceGen.Instance);
+ Add(TextPadGen.Instance);
Add(typeof(SumFunc), SumBaseGen.Instance);
Add(typeof(MeanFunc), SumBaseGen.Instance);
diff --git a/src/Core/Rexl.Code/Operations/Funcs/Text.cs b/src/Core/Rexl.Code/Operations/Funcs/Text.cs
index 7cd8827..514050b 100644
--- a/src/Core/Rexl.Code/Operations/Funcs/Text.cs
+++ b/src/Core/Rexl.Code/Operations/Funcs/Text.cs
@@ -293,3 +293,22 @@ protected override bool TryGetMeth(ICodeGen codeGen, BndCallNode call, out Metho
return true;
}
}
+
+public sealed class TextPadGen : GetMethGen
+{
+ public static readonly TextPadGen Instance = new TextPadGen();
+
+ private TextPadGen()
+ {
+ }
+
+ protected override bool TryGetMeth(ICodeGen codeGen, BndCallNode call, out MethodInfo meth)
+ {
+ Validation.AssertValue(codeGen);
+ Validation.Assert(IsValidCall(call, true));
+
+ var fn = GetOper(call);
+ meth = fn.Map.Method;
+ return true;
+ }
+}
diff --git a/src/Test/Rexl.Bind.Test/Scripts/Binder/Functions/String.txt b/src/Test/Rexl.Bind.Test/Scripts/Binder/Functions/String.txt
index 6e89e64..8b69a23 100644
--- a/src/Test/Rexl.Bind.Test/Scripts/Binder/Functions/String.txt
+++ b/src/Test/Rexl.Bind.Test/Scripts/Binder/Functions/String.txt
@@ -238,6 +238,43 @@ Text.Replace("ABC", "X", s)
Text.Replace("ABC", "B", "X")
Text.Replace("ABACDAE", "A", "!!")
+Text.PadStart(s, i8)
+Text.PadStart(null, 0)
+Text.PadStart("hello", -1)
+Text.PadStart(s, -1)
+Text.PadStart("hello", 10)
+Text.PadStart(s, 10)
+Text.PadStart(s, s)
+Text.PadStart(s, u2)
+Text.PadStart(s, qi8)
+Text.PadStart(s, qu2)
+Text.PadStart(i8, i8)
+
+Text.PadEnd(s, i8)
+Text.PadEnd(null, 0)
+Text.PadEnd("hello", -1)
+Text.PadEnd(s, -1)
+Text.PadEnd("hello", 10)
+Text.PadEnd(s, 10)
+Text.PadEnd(s, s)
+Text.PadEnd(s, u2)
+Text.PadEnd(s, qi8)
+Text.PadEnd(s, qu2)
+Text.PadEnd(i8, i8)
+
+Tex.Pad(s, i8)
+Text.PadCenter(s, i8)
+Text.Pad(null, 15)
+Text.Pad("hello", -1)
+Text.Pad(s, -1)
+Text.Pad("hello", 10)
+Text.Pad(s, 10)
+Text.Pad(s, s)
+Text.Pad(s, u2)
+Text.Pad(s, qi8)
+Text.Pad(s, qu2)
+Text.Pad(i8, i8)
+
// Lifting.
:: {g:g*, o:o*, s:s*, b:b*, qb:b?*, d:d*, n:n*, qn:n?*, r8:r8*, qr8:r8?*, r4:r4*, qr4:r4?*, i:i*, qi:i?*, i8:i8*, qi8:i8?*, i4:i4*, qi4:i4?*, i2:i2*, qi2:i2?*, i1:i1*, qi1:i1?*, u8:u8*, qu8:u8?*, u4:u4*, qu4:u4?*, u2:u2*, qu2:u2?*, u1:u1*, qu1:u1?*}
@@ -360,3 +397,51 @@ Text.Replace("ABC", s, s)
Text.Replace(s, s, s)
Text.Replace(s, "", s)
+
+Text.PadStart(s, i8)
+Text.PadStart(null, 0)
+Text.PadStart("hello", -1)
+Text.PadStart("hello", [3, 4, 1])
+Text.PadStart(s, [3, 4, 1])
+Text.PadStart(s, -1)
+Text.PadStart("hello", 10)
+Text.PadStart(["hello", "howdy", null], 10)
+Text.PadStart(s, 10)
+Text.PadStart(s, s)
+Text.PadStart(s, u1)
+Text.PadStart(s, u2)
+Text.PadStart(s, u4)
+Text.PadStart(s, u8)
+Text.PadStart(s, qi8)
+Text.PadStart(s, qu2)
+
+Text.PadEnd(s, i8)
+Text.PadEnd(null, 0)
+Text.PadEnd(s, -1)
+Text.PadEnd(s, [3, 4, 1])
+Text.PadEnd("hello", [3, 4, 9])
+Text.PadEnd(["hello", "howdy", null], 10)
+Text.PadEnd(s, 10)
+Text.PadEnd(s, s)
+Text.PadEnd(s, u1)
+Text.PadEnd(s, u2)
+Text.PadEnd(s, u4)
+Text.PadEnd(s, u8)
+Text.PadEnd(s, qi8)
+Text.PadEnd(s, qu2)
+
+Text.PadCenter(s, i8)
+Text.PadCenter(null, 0)
+Text.PadCenter("hello", -1)
+Text.PadCenter("hello", [3, 4, 9])
+Text.PadCenter(s, -1)
+Text.PadCenter(s, [3, 4, 1])
+Text.PadCenter(["hello", "howdy", null], 10)
+Text.PadCenter(s, 10)
+Text.PadCenter(s, s)
+Text.PadCenter(s, u1)
+Text.PadCenter(s, u2)
+Text.PadCenter(s, u4)
+Text.PadCenter(s, u8)
+Text.PadCenter(s, qi8)
+Text.PadCenter(s, qu2)
diff --git a/src/Test/Rexl.Code.Test/Scripts/CodeGen/Functions/String.txt b/src/Test/Rexl.Code.Test/Scripts/CodeGen/Functions/String.txt
index 3705ef1..d12f089 100644
--- a/src/Test/Rexl.Code.Test/Scripts/CodeGen/Functions/String.txt
+++ b/src/Test/Rexl.Code.Test/Scripts/CodeGen/Functions/String.txt
@@ -1,6 +1,6 @@
::: {g:g, o:o, s:s, b:b, qb:b?, d:d, qd:d?, n:n, qn:n?, r8:r8, qr8:r8?, r4:r4, qr4:r4?, i:i, qi:i?, i8:i8, qi8:i8?, i4:i4, qi4:i4?, i2:i2, qi2:i2?, i1:i1, qi1:i1?, u8:u8, qu8:u8?, u4:u4, qu4:u4?, u2:u2, qu2:u2?, u1:u1, qu1:u1?}
-``` N := First([null, "hello"]);
+``` N := Null("hello");
Text.Part(Wrap(""), 0)
Text.Part(Wrap(""), 1)
@@ -71,3 +71,20 @@ Text.TrimEnd(Wrap(Null(""))) | With(_, {S: it, L: Text.Len(it)})
Text.Replace(N, "A", "B")
Text.Replace("A", N, "B")
Text.Replace("ABC", Wrap("B"), N)
+
+Text.PadStart(N, 0)
+Text.PadStart(N, Wrap(10))
+Text.PadStart("hello", Wrap(-1))
+Text.PadStart("hello", Wrap(10))
+
+Text.PadEnd(N, 0)
+Text.PadEnd(N, Wrap(10))
+Text.PadEnd("hello", -1)
+Text.PadEnd("hello", 10)
+Text.PadEnd(Wrap("hello"), 10)
+
+Text.Pad(N, 0)
+Text.Pad(N, Wrap(10))
+Text.PadCenter("hello", -1)
+Text.Pad("hello", 10)
+Text.Pad(Wrap("hello"), 10)