using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Ariadne.Generator
{
///
/// A call in a passage to a passage that runs with a frame of its own, and what the caller holds while it's paused
/// inside it. That is a passage of another arc, or a passage that owns a frame, whose locals can't live in the
/// arc's frame.
///
public class ForeignSite
{
public const string ReceiverSuffix = "receiver";
///
/// Holds the passage called, as its own class declares it.
///
public readonly IMethodSymbol Passage;
///
/// Holds the script class that declares the passage.
///
public readonly ScriptClass Owner;
///
/// Holds the expression for the arc the passage runs on, which is worked out once when the call begins. It's
/// null for a call on the calling arc, which only a passage that owns a frame needs a site for.
///
public readonly ExpressionSyntax Receiver;
///
/// Holds the type of that expression.
///
public readonly ITypeSymbol ReceiverType;
///
/// Holds the type arguments the call gives a generic passage, as <int, string>, or "" for any
/// other.
///
public readonly string TypeArguments;
///
/// Holds the name of the table the callee's locals are saved in, among the calling arc's. It ends with a
/// slash, which tells it from a local.
///
public readonly string ScopeName;
///
/// Holds the name of the field in the calling arc's frame that keeps the receiver.
///
public readonly string ReceiverField;
///
/// Holds the name of the field in the calling arc's frame that keeps the callee's frame.
///
public readonly string FrameField;
///
/// Holds the type of the callee's frame. For a passage on this arc that owns a frame that is its own frame,
/// and for one on another arc it is the frame of that arc, which the owned frame is lent beside.
///
public readonly string FrameType;
///
/// Holds the name of the field in the calling arc's frame that keeps the owned frame of a passage called on
/// another arc, or null for any other call.
///
public readonly string OwnedFrameField;
///
/// Holds the type of that owned frame, or null.
///
public readonly string OwnedFrameType;
///
/// Holds the call, which a save library's analyzer reports a problem with the receiver's type on.
///
public readonly Location Where;
///
/// Holds the open generic frame class a polymorphic call builds its callee's frame from when it loads, as
/// Wrap__Frame<,>, or null for any other call.
///
public readonly string FrameDefinition;
///
/// Holds the call's type arguments as typeof expressions, to save beside the callee's locals. It is
/// null for any call that isn't polymorphic.
///
public readonly string TypeArgumentTypes;
///
/// Whether the call is through base, eg: base.Run. The base class's passage keeps its locals in a frame
/// of the base class's type, made for the call, so that they stay apart from the locals of the one that calls
/// it.
///
public readonly bool IsBaseCall;
private ForeignSite(IMethodSymbol passage, ScriptClass owner, ExpressionSyntax receiver,
ITypeSymbol receiverType, string typeArguments, string frameType, string ownedFrameType, string scopeName,
string fieldStem, Location where, string frameDefinition = null, string typeArgumentTypes = null,
bool isBaseCall = false)
{
IsBaseCall = isBaseCall;
FrameDefinition = frameDefinition;
TypeArgumentTypes = typeArgumentTypes;
Passage = passage;
Owner = owner;
Receiver = receiver;
ReceiverType = receiverType;
TypeArguments = typeArguments;
ScopeName = scopeName;
ReceiverField = fieldStem + "_" + ReceiverSuffix;
FrameField = fieldStem + "_frame";
FrameType = frameType;
OwnedFrameType = ownedFrameType;
if (ownedFrameType != null)
{
OwnedFrameField = fieldStem + "_ownedFrame";
}
Where = where;
}
///
/// Gets whether the call is to a passage on the calling arc.
///
public bool OnThisArc
{
get
{
return Receiver == null;
}
}
///
/// Gets whether the call is to a passage that owns a frame on another arc, which runs with that frame beside
/// the frame of that arc.
///
public bool HasOwnedFrame
{
get
{
return OwnedFrameType != null;
}
}
///
/// Gets whether the call is to a passage in the caller's own cycle with a type argument built from the
/// caller's type parameters, so each lap's frame is a type no earlier lap made.
///
public bool Polymorphic => FrameDefinition != null;
///
/// Gets the type the field that keeps the callee's frame is declared as. A polymorphic call uses the base
/// frame type, since every lap's frame is a type the last didn't have.
///
public string FrameFieldType => Polymorphic ? "global::Ariadne.Frame" : FrameType;
///
/// Gets the expression a passage copy reads the callee's frame through, as the callee's frame type.
///
public string TypedFrameAccess => Polymorphic ? "((" + FrameType + ")" + FrameAccess + ")" : FrameAccess;
///
/// Gets the expression a passage copy reads the receiver through.
///
public string ReceiverAccess
{
get
{
return PassageWriter.FrameName + "." + ReceiverField;
}
}
///
/// Gets the expression a passage copy reads the callee's frame through.
///
public string FrameAccess
{
get
{
return PassageWriter.FrameName + "." + FrameField;
}
}
///
/// Gets the expression a passage copy reads the owned frame through.
///
public string OwnedFrameAccess
{
get
{
return PassageWriter.FrameName + "." + OwnedFrameField;
}
}
///
/// Gets the key the receiver is told apart by in the calling arc's frame, which saves it as
/// in the callee's table.
///
public string ReceiverKey
{
get
{
return ScopeName + ReceiverSuffix;
}
}
///
/// Returns the site of , made in the passage named at the
/// pause keyed .
///
public static ForeignSite Of(ScriptClass script, string callerName, InvocationExpressionSyntax call,
string key, IMethodSymbol passage)
{
ExpressionSyntax receiver = ((MemberAccessExpressionSyntax)call.Expression).Expression;
ITypeSymbol receiverType = script.ModelFor(receiver).GetTypeInfo(receiver).Type;
ScriptClass owner = script.OwnerOf(passage);
// The callee's frame is typed as the owner is constructed at the call, eg: Plant.__Frame.
var target = (IMethodSymbol)script.ModelFor(call).GetSymbolInfo(call).Symbol;
string ownerType = target.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
string frameType = ownerType + "." + PassageWriter.FrameTypeName;
string typeArguments = "";
string ownedFrameType = null;
if (script.OwnsFrame(passage))
{
typeArguments = TypeArgumentsOf(target);
ownedFrameType = ownerType + "." + PassageWriter.OwnedFrameName(passage) + typeArguments;
}
return new ForeignSite(passage, owner, receiver, receiverType ?? owner.Type, typeArguments, frameType,
ownedFrameType, callerName + "." + key + "/", FieldStemOf(callerName, key), call.GetLocation());
}
// A key is a label or a passage name, with #1, #2 after it for a second pause under the same label. The #
// can't be in an identifier.
private static string FieldStemOf(string callerName, string key)
{
return callerName + "____call_" + key.Replace("#", "__n");
}
private static string TypeArgumentsOf(IMethodSymbol target)
{
if (target.TypeArguments.Length == 0)
{
return "";
}
return "<" + string.Join(", ", target.TypeArguments.Select(argument =>
argument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) + ">";
}
///
/// Returns the site of a call to base.Run, made in the passage named at
/// the pause keyed .
///
public static ForeignSite OfBase(ScriptClass script, string callerName, InvocationExpressionSyntax call,
string key, IMethodSymbol passage)
{
// Both Runs would otherwise save their locals under the same keys, and the arc's frame holds only one set.
var target = (IMethodSymbol)script.ModelFor(call).GetSymbolInfo(call).Symbol;
string frameType = target.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + "." +
PassageWriter.FrameTypeName;
return new ForeignSite(passage, script.OwnerOf(passage), null, null, "", frameType, null,
callerName + "." + key + "/", FieldStemOf(callerName, key), call.GetLocation(), isBaseCall: true);
}
///
/// Returns the site of to a passage on this arc that owns a frame, made in the passage
/// named at the pause keyed .
///
public static ForeignSite OfOwnedFrame(ScriptClass script, string callerName,
InvocationExpressionSyntax call, string key, IMethodSymbol passage)
{
var target = (IMethodSymbol)script.ModelFor(call).GetSymbolInfo(call).Symbol;
string typeArguments = TypeArgumentsOf(target);
// The frame is a class of the arc that declares the passage, constructed with the call's type arguments.
string frameClass = target.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + "." +
PassageWriter.OwnedFrameName(passage);
string frameDefinition = null;
string typeArgumentTypes = null;
IMethodSymbol caller = script.Passages.FirstOrDefault(each => each.Name == callerName);
if (caller != null && script.InSameCycle(caller, passage)
&& target.TypeArguments.Any(argument => ScriptChecks.BuiltFrom(argument, caller.TypeParameters)))
{
frameDefinition = frameClass + "<" + new string(',', target.TypeArguments.Length - 1) + ">";
typeArgumentTypes = string.Join(", ", target.TypeArguments.Select(argument =>
"typeof(" + argument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + ")"));
}
return new ForeignSite(passage, script.OwnerOf(passage), null, null, typeArguments,
frameClass + typeArguments, null, callerName + "." + key + "/", FieldStemOf(callerName, key),
call.GetLocation(), frameDefinition, typeArgumentTypes);
}
}
}