Skip to content

Instantly share code, notes, and snippets.

@erkrali
Created October 30, 2023 18:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erkrali/d48ed754b68a26e326b61aad78d43ab7 to your computer and use it in GitHub Desktop.
Save erkrali/d48ed754b68a26e326b61aad78d43ab7 to your computer and use it in GitHub Desktop.
mixin-based string interpolation in D with implicit conversion to string
module interpolation;
import std.meta;
public:
private struct InterpolatedBlockTag { }
auto interpolatedBlock(string _preText, string _format, string _originalSymbol, T)(auto ref T val) {
static struct InterpolatedBlock {
public:
alias Tag = InterpolatedBlockTag;
alias preText = _preText;
alias format = _format;
alias originalSymbol = _originalSymbol;
alias tuple = AliasSeq!(preText, format, val);
T val;
string toString()() const {
import std.conv;
return(preText ~ val.to!string);
}
alias toString this;
private:
this(T val) {
this.val = val;
}
}
return InterpolatedBlock(val);
}
template isInterpolatedBlock(alias T) {
static if (is(T)) {
enum isInterpolatedBlock = is(T.Tag == InterpolatedBlockTag);
} else {
enum isInterpolatedBlock = is(typeof(T).Tag == InterpolatedBlockTag);
}
}
unittest {
auto block = interpolatedBlock!("Hello, ", "", "name")("John");
static assert(isInterpolatedBlock!block);
assert(block == "Hello, John");
static assert(block.preText == "Hello, ");
static assert(block.format == "");
assert(block.val == "John");
static assert(block.originalSymbol == "name");
}
private struct InterpolatedStringTag { }
auto interpolatedString(InterpolatedBlocks...)(InterpolatedBlocks blocks) {
static assert (allSatisfy!(isInterpolatedBlock,InterpolatedBlocks), "You can only construct an interpolated string out of interpolated blocks. Got ", InterpolatedBlocks);
static struct InterpolatedString {
public:
alias Tag = InterpolatedStringTag;
InterpolatedBlocks blocks;
static if (InterpolatedBlocks.length > 0) {
@disable this();
}
@disable this(this);
alias toString this;
string toString()() const {
string result;
static foreach(block;blocks) {
result ~= block.toString;
}
return result;
}
template _tuple() {
alias _tuple = AliasSeq!();
static foreach(i; 0..blocks.length) {
_tuple = AliasSeq!(_tuple, InterpolatedBlocks[i].preText, InterpolatedBlocks[i].format, vals[i]);
}
}
alias tuple = _tuple!();
private:
static if (InterpolatedBlocks.length > 0) {
this(InterpolatedBlocks blocks) {
foreach (i, block ; blocks) {
this.blocks[i] = block;
this.vals[i] = block.val;
}
}
}
staticMap!(valType, InterpolatedBlocks) vals;
template valType(InterpolatedBlock) if (isInterpolatedBlock!InterpolatedBlock){
alias valType = typeof(InterpolatedBlock.val);
}
}
return InterpolatedString(blocks);
}
template isInterpolatedString(alias T) {
static if (is (T)) {
enum isInterpolatedString = is(T.Tag == InterpolatedStringTag);
} else {
enum isInterpolatedString = is(typeof(T).Tag == InterpolatedStringTag);
}
}
unittest {
import std.logger;
auto iString = interpolatedString(
interpolatedBlock!("Hello, ", "", "firstName")("John"),
interpolatedBlock!(" ", "", "lastName")("Smith")
);
static assert(isInterpolatedString!iString);
assert(iString == "Hello, John Smith");
static assert(iString.tuple[0] == "Hello, ");
static assert(iString.tuple[1] == "");
assert(iString.blocks[0].tuple[2] == "John");
static assert(iString.blocks[0].originalSymbol == "firstName");
static assert(iString.tuple[3] == " ");
static assert(iString.tuple[4] == "");
assert(iString.tuple[5] == "Smith");
static assert(iString.blocks[1].originalSymbol == "lastName");
}
private string interpolate(string s)() {
import std.logger;
string output = "interpolatedString(";
int bracketDepth = 0;
int formatBracketDepth = 0;
string preText = "";
string format = "";
string originalSymbol = "";
string current;
for(int i = 0; i < s.length; i++) {
if (s[i] == '\\') {
i++;
assert (i < s.length, "Unterminated escape sequence");
current ~= s[i];
} else if (bracketDepth == 0 && i + 1 < s.length && s[i] == '$' && s[i+1] == '(') { // "$(" starts the interpolation
preText = current;
current = "";
i++;
bracketDepth = 1;
if (i + 1 < s.length && s[i+1] == '{') { // "$({" starts a formatted interpolation
formatBracketDepth = 1;
i++;
}
} else if (bracketDepth > 0 && formatBracketDepth == 0 && s[i] == '(') {
bracketDepth++;
current ~= '(';
} else if (formatBracketDepth > 0 && s[i] == '{') {
formatBracketDepth++;
current ~= '{';
} else if (formatBracketDepth > 0 && s[i] == '}') {
formatBracketDepth--;
if (formatBracketDepth == 0) {
format = current;
current = "";
} else {
current ~= '}';
}
} else if (bracketDepth > 0 && formatBracketDepth == 0 && s[i] == ')') {
bracketDepth--;
if (bracketDepth == 0) {
originalSymbol = current;
current = "";
output ~= "interpolatedBlock!(\"" ~
preText ~ "\",\"" ~
format ~ "\", \"" ~
originalSymbol ~ "\")(" ~
originalSymbol ~ "),";
preText = "";
format = "";
originalSymbol = "";
} else {
current ~= ')';
}
} else {
current ~= s[i];
}
}
assert(bracketDepth == 0 && formatBracketDepth == 0, "Improperly terminated interpolation string.");
if (preText != "" || format != "" || originalSymbol != "") {
output ~= "interpolatedBlock!(\"" ~
preText ~ "\",\"" ~
format ~ "\", \"" ~
originalSymbol ~ "\")(" ~
originalSymbol ~ "),";
}
output ~= ")";
return output;
}
enum i(string s) = interpolate!s;
enum iS(string s) = interpolate!s ~ ".toString";
enum it(string s) = interpolate!s ~ ".tuple";
unittest {
static assert (i!"" == `interpolatedString()`);
static assert (i!"Hello, $({%30s}name)" == `interpolatedString(interpolatedBlock!("Hello, ","%30s", "name")(name),)`);
static assert (i!"Hello, $({{%30x)}}(name))" == `interpolatedString(interpolatedBlock!("Hello, ","{%30x)}", "(name)")((name)),)`);
assert(mixin(i!``) == "");
string firstName = "John";
string lastName = "Smith";
string greeting = mixin(i!`Hello, $(firstName) $(lastName)`);
assert(greeting == "Hello, John Smith");
auto greetingRaw = mixin(i!`Hello, $(firstName) $(lastName)`);
static assert(isInterpolatedString!greetingRaw);
() @nogc {
int ii = 1;
assert(mixin(it!`The number is $(ii)`) == AliasSeq!("The number is ", "", 1));
}();
static struct S {
int i;
static numCalls = 0;
this(int i) {
this.i = i;
S.numCalls++;
}
string toString() const {
import std.conv : to;
return i.to!string;
}
}
assert(mixin(i!`The number is $(S(1))`) == "The number is 1");
assert(S.numCalls == 1);
struct S2 {
import std.traits;
static string fun(T)(T s) if (!isInterpolatedString!(T) && isSomeString!T) {
return "string";
}
static string fun(T)(T t) if (isInterpolatedString!(T)) {
return "interpolated";
}
}
assert(S2.fun("") == "string");
assert(S2.fun(mixin(i!``)) == "interpolated");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment