Skip to content

Instantly share code, notes, and snippets.

@andrewabest
Created February 4, 2022 00:43
Show Gist options
  • Save andrewabest/84ea268ab9e5b43803929c1d774467b5 to your computer and use it in GitHub Desktop.
Save andrewabest/84ea268ab9e5b43803929c1d774467b5 to your computer and use it in GitHub Desktop.
A basic function-applying token replacement implementation with Sprache
void Main()
{
var input = "Hello #{Greeting | Upper} #{Greeting | Upper}, How do you like #{DayOfWeek | Lower}";
var variables = new Dictionary<string, string>()
{
{ "Greeting", "hello" },
{ "DayOfWeek", "Wednesday" }
};
var tokens = ParseInput(input);
tokens.Dump();
foreach (var token in tokens)
{
input = token.Replace(input, variables[token.Id]);
}
input.Dump();
}
public interface IPipeFunction
{
string Apply(string input);
}
public class Upper : IPipeFunction
{
public string Apply(string input)
{
return input.ToUpperInvariant();
}
}
public class Lower : IPipeFunction
{
public string Apply(string input)
{
return input.ToLowerInvariant();
}
}
IEnumerable<Token> ParseInput(string input)
{
var pipeFunctions = new Dictionary<string, Func<IPipeFunction>>()
{
{ "Upper", () => new Upper() },
{ "Lower", () => new Lower() },
};
var tokenStart = Parse.String("#{");
var tokenEnd = Parse.Char('}');
var pipe = Parse.Char('|');
var functionParser =
from space in Parse.WhiteSpace.Once()
from separator in pipe
from anotherSpace in Parse.WhiteSpace.Once()
from functionName in Parse.Letter.Many().Text()
select pipeFunctions[functionName]();
var tokenParser =
from start in tokenStart
from token in Parse.AnyChar.Except(functionParser).Many().Text()
from function in functionParser
from end in tokenEnd
select new Token() { Id = token, PipeFunction = function };
var inputParser =
from preamble in Parse.AnyChar.Except(tokenStart).Many().Optional()
from token in tokenParser
select token;
return inputParser.Many().Parse(input);
}
public class Token
{
public string Id {get;set;}
public IPipeFunction? PipeFunction {get;set;}
string Format => PipeFunction == null ? $"#{{{Id}}}" : $"#{{{Id} | {PipeFunction.GetType().Name}}}";
public string Replace(string input, string value)
{
return input.Replace(Format, PipeFunction != null ? PipeFunction.Apply(value) : value);
}
}
@geofflamrock
Copy link

To use with vscode notebooks + .net interactive you can replace the Main function with:

#r "nuget:Sprache"

using Sprache;

var input = "Hello #{Greeting | Upper} #{Greeting | Upper}, How do you like #{DayOfWeek | Lower}";

var variables = new Dictionary<string, string>()
{
    { "Greeting", "hello" },
    { "DayOfWeek", "Wednesday" }
};

var tokens = ParseInput(input);

display(tokens);

foreach (var token in tokens)
{
    input = token.Replace(input, variables[token.Id]);
}

display(input);

Copy link

ghost commented Feb 4, 2022

Another way to define types is to change this:

var pipeFunctions = new Dictionary<string, Func<IPipeFunction>>()
{
	{ "Upper", () => new Upper() },
	{ "Lower", () => new Lower() },
};

to

var pipeFunctions = new Dictionary<string, Func<string, string>>()
{
	{ "Upper", s => s.ToUpperInvariant() },
	{ "Lower", s => s.ToLowerInvariant() },
};

and then (with a bit of modification here and there) we don't need to define IPipeFunction and Upper and Lower any more.

If we do want to give Func<string, string> a special name, we can add this to the top:

using PipeFunction = System.Func<string, string>;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment