🚧 write intro
A SharpGrammar grammar is created by defining at least one Processable
. A Processable
is a building block that can be evaluated within a given context. Evaluation of a processable is triggered by calling its .Process()
method.
var food = Take.OneOf("pizza", "salad");
// prints either "pizza" or "salad"
Console.WriteLine(food.Process());
Processables can be concatenated with the +
operator and values like strings are implicitly converted.
// note that dinnerSentence is not a string, but a Processable.
var dinnerSentence = "Today I'll have " + food + " for dinner.";
// prints either "Today I'll have pizza for dinner."
// or "Today I'll have salad for dinner."
Console.WriteLine(dinnerSentence.Process());
Note that the strings "Today I'll have " and " for dinner." are implicitly converted to processables that always evaluate to the respective strings.
With the intention to make composition of processables more readable, they are created through factory-pattern methods, implicit conversion and operators (instead of directly calling constructors).
Instead of calling ...
Processable<string> na = new ValueProcessable<string>("na");
Processable<string> character = new OneOfProcessable<string>(" Batman!", " Robin!");
Processable<string> sentence =
new ProcessableList<string>(new RepeatProcessable<string>(na, 8), character);
... you call ...
Processable<string> na = "na";
Processable<string> character = Take.OneOf<string>(" Batman!", " Robin!");
Processable<string> song = na.Repeat(8) + character;
Every processable is processed within a context. The context is provided by the IContext
interface. IContext
provides basic random number generation and is extensible with modules. To process a Processable
within a specific context, just pass the context into the .Process(IContext context)
method.
var context = new Context(1337);
Console.WriteLine(food.Process(context));
Although a new context is implicitly created if you don't provide one, creating a context explicitly allows you to specify the seed used for the random number generation and add extension modules.
SharpGrammar provides some built-in extension modules that add extra functionality like counting or memory functionality. (see list below)
Some of these heavily utilize the context, like the processables in the Memory
namespace. The context does not provide this functionality out-of-the-box, instead a module needs to be added.
This can be done with the .BindModule()
method either explicitly from an actual instance...
var context = new Context();
var module = new SomeModule();
context.BindModule(module);
...or implicitly by type, if the type has a parameterless constructor.
var context = new Context();
context.BindModule<SomeModule>();
The signature of the .BindModule()
method allows for a procedural, fluid use.
var context = new Context()
.BindModule<SomeModule>()
.BindModule<AnotherModule>()
.BindModule(new YetAnotherModule(someParameter));
🚧 link all entries to their own documentation pages
Module | Functionality |
---|---|
Counting | The Counting module provides integer memory and counting functionality. |
Memory | The Memory module provides functionality to save and recall values. |
The built-in processables cover a lot of common use-cases for generative grammars, but sometimes custom functionality is needed. You can easily provide that with custom written processables. Here is an example of a Processable<string>
that converts a provided Processable<string>
to lower case:
internal record ToLowerProcessable : Processable<string>
{
private readonly Processable<string> value;
internal ToLowerProcessable(Processable<string> value)
{
this.value = value ??
throw new NullReferenceException(nameof(value));
}
public override string Process(IContext<string> context) =>
value.Process(context).ToLower();
}
The processable itself and its constructor(s) are kept internal
and are not meant to be exposed as API. Instead a static API class is used:
static class StringProcessingExtensions
{
public static Processable<string> ToLower(this Processable<string> processable) =>
new ToLowerProcessable(processable);
}
Modules have no restrictions. They can be bound to a context as seen above (Adding Functionality) and can be retrieved via the .Get<TModule>()
method. These modules' functionality can be used by processables that need some level of consitency or state.
context.Get<MyModule>()