-
Notifications
You must be signed in to change notification settings - Fork 2
Reference Handling
To discuss reference handling, let's see an example of a function definition and function call. First, we will see a solution which might seem obvious at first sight, but which does not work:
In the domain:
public class Program
{
public Name Name { get; set; }
public IList<Function> Functions { get; set; }
}
public class Function
{
public Name Name { get; set; }
public Type? ReturnType { get; set; }
public IList<Parameter> Parameters { get; set; }
public IList<Statement> Body { get; set; }
}
public abstract class Statement
{
}
public class FunctionCall : Statement
{
public Function FunctionReference { get; set; }
public IList<Argument> Arguments { get; set; }
}
In the grammar:
var Program = new BnfiTermRecord<D.Program>();
var Function = new BnfiTermRecord<D.Function>();
var Statement = new BnfiTermChoice<D.Statement>();
var FunctionCall = new BnfiTermRecord<D.FunctionCall>();
var FunctionReference = new BnfiTermConversion<D.Function>();
Program.Rule =
PROGRAM
+ Name.BindTo(Program, t => t.Name)
+ Function.StarList().BindTo(Program, t => t.Functions)
;
Function.Rule =
FUNCTION
+ Name.BindTo(Function, t => t.Name)
+ LEFT_PAREN
+ Parameter.StarList(COMMA).BindTo(Function, t => t.Parameters)
+ RIGHT_PAREN
+ (COLON + Type).QVal().BindTo(Function, t => t.ReturnType)
+ BEGIN
+ Statement.PlusList().BindTo(Function, t => t.Body)
+ END
;
Statement.SetRuleOr(
FunctionCall + SEMICOLON,
);
FunctionCall.Rule =
FunctionReference.BindTo(FunctionCall, t => t.FunctionReference)
+ LEFT_PAREN
+ Argument.StarList(COMMA).BindTo(FunctionCall, t => t.Arguments)
+ RIGHT_PAREN
;
FunctionReference.Rule =
NameRef.ConvertValue(_name => CreateFunctionFromNameByMagic(), _function => _function.Name)
;
Now, our problem is that the CreateFunctionFromNameByMagic
cannot be implemented. We should return the function by name which function might not even exist at that time, because the parser might have not parsed it yet. We could create a "dummy" function, but we should also remember the name of the function, and put it somewhere. Then we could bind the reference later to the real function, and throw away the dummy function.
In fact, we are doing something like that by introducing the typesafe Sarcasm.DomainCore.Reference<T>
type, where T
is the type of the referred object.
In the domain:
public class Program
{
public Name Name { get; set; }
public IList<Function> Functions { get; set; }
}
public class Function
{
public Name Name { get; set; }
public Type? ReturnType { get; set; }
public IList<Parameter> Parameters { get; set; }
public IList<Statement> Body { get; set; }
}
public abstract class Statement
{
}
public class FunctionCall : Statement
{
public Reference<Function> FunctionReference { get; set; } // reference!
public IList<Argument> Arguments { get; set; }
}
In the grammar:
var Program = new BnfiTermRecord<D.Program>();
var Function = new BnfiTermRecord<D.Function>();
var Statement = new BnfiTermChoice<D.Statement>();
var FunctionCall = new BnfiTermRecord<D.FunctionCall>();
var FunctionReference = new BnfiTermConversion<Reference<D.Function>>(); // reference!
Program.Rule =
PROGRAM
+ Name.BindTo(Program, t => t.Name)
+ Function.StarList().BindTo(Program, t => t.Functions)
;
Function.Rule =
FUNCTION
+ Name.BindTo(Function, t => t.Name)
+ LEFT_PAREN
+ Parameter.StarList(COMMA).BindTo(Function, t => t.Parameters)
+ RIGHT_PAREN
+ (COLON + Type).QVal().BindTo(Function, t => t.ReturnType)
+ BEGIN
+ Statement.PlusList().BindTo(Function, t => t.Body)
+ END
;
Statement.SetRuleOr(
FunctionCall + SEMICOLON,
);
FunctionCall.Rule =
FunctionReference.BindTo(FunctionCall, t => t.FunctionReference)
+ LEFT_PAREN
+ Argument.StarList(COMMA).BindTo(FunctionCall, t => t.Arguments)
+ RIGHT_PAREN
;
FunctionReference.Rule =
NameRef.ConvertValue(
_nameRef => ReferenceFactory.Get<D.Function>(_nameRef), // get the function reference and store the nameref inside
_functionReference => _functionReference.NameRef
)
;
Reference
is a type that can hold the name of the referred object (called NameRef
) that we have during the parsing, and the actual reference to the object which can be set after the parsing using the nameref.
Although using Reference
needs modifications in your domain (or that you define your domain slightly differently than otherwise), but actually it's more expressive than using pure C# references. If you look at the old domain, you can see this:
public class Program
{
public Name Name { get; set; }
public IList<Function> Functions { get; set; }
}
public class FunctionCall : Statement
{
public Function FunctionReference { get; set; }
public IList<Argument> Arguments { get; set; }
}
Both FunctionCall
's FunctionReference
property and an item of Program
's Functions
property has the same type Function
, despite the fact that they are totally different: a program owns its functions (composition, or aggregation, if you like), while a function call only refers to a function (association).
In C# we have references only, therefore the two roles cannot be distinguished. However, we can still express that they are different by using the Reference
type as in the example above (which might have been given the name "Association", but that's just a matter of terminology):
public class Program
{
public Name Name { get; set; }
public IList<Function> Functions { get; set; } // owns functions
}
public class FunctionCall : Statement
{
public Reference<Function> FunctionReference { get; set; } // refers to a function
public IList<Argument> Arguments { get; set; }
}
If you are interested in Sarcasm's general unparser, continue with Unparser.