mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 10:34:27 +11:00
Committed everything
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ink.Parsed
|
||||
{
|
||||
// Base class for Knots and Stitches
|
||||
public abstract class FlowBase : Parsed.Object, INamedContent
|
||||
{
|
||||
public class Argument
|
||||
{
|
||||
public Identifier identifier;
|
||||
public bool isByReference;
|
||||
public bool isDivertTarget;
|
||||
}
|
||||
|
||||
public string name
|
||||
{
|
||||
get { return identifier?.name; }
|
||||
}
|
||||
public Identifier identifier { get; set; }
|
||||
public List<Argument> arguments { get; protected set; }
|
||||
public bool hasParameters { get { return arguments != null && arguments.Count > 0; } }
|
||||
public Dictionary<string, VariableAssignment> variableDeclarations;
|
||||
|
||||
public abstract FlowLevel flowLevel { get; }
|
||||
public bool isFunction { get; protected set; }
|
||||
|
||||
public FlowBase (Identifier name = null, List<Parsed.Object> topLevelObjects = null, List<Argument> arguments = null, bool isFunction = false, bool isIncludedStory = false)
|
||||
{
|
||||
this.identifier = name;
|
||||
|
||||
if (topLevelObjects == null) {
|
||||
topLevelObjects = new List<Parsed.Object> ();
|
||||
}
|
||||
|
||||
// Used by story to add includes
|
||||
PreProcessTopLevelObjects (topLevelObjects);
|
||||
|
||||
topLevelObjects = SplitWeaveAndSubFlowContent (topLevelObjects, isRootStory:this is Story && !isIncludedStory);
|
||||
|
||||
AddContent(topLevelObjects);
|
||||
|
||||
this.arguments = arguments;
|
||||
this.isFunction = isFunction;
|
||||
this.variableDeclarations = new Dictionary<string, VariableAssignment> ();
|
||||
}
|
||||
|
||||
List<Parsed.Object> SplitWeaveAndSubFlowContent(List<Parsed.Object> contentObjs, bool isRootStory)
|
||||
{
|
||||
var weaveObjs = new List<Parsed.Object> ();
|
||||
var subFlowObjs = new List<Parsed.Object> ();
|
||||
|
||||
_subFlowsByName = new Dictionary<string, FlowBase> ();
|
||||
|
||||
foreach (var obj in contentObjs) {
|
||||
|
||||
var subFlow = obj as FlowBase;
|
||||
if (subFlow) {
|
||||
if (_firstChildFlow == null)
|
||||
_firstChildFlow = subFlow;
|
||||
|
||||
subFlowObjs.Add (obj);
|
||||
_subFlowsByName [subFlow.identifier?.name] = subFlow;
|
||||
} else {
|
||||
weaveObjs.Add (obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Implicit final gather in top level story for ending without warning that you run out of content
|
||||
if (isRootStory) {
|
||||
weaveObjs.Add (new Gather (null, 1));
|
||||
weaveObjs.Add (new Divert (new Path (Identifier.Done)));
|
||||
}
|
||||
|
||||
var finalContent = new List<Parsed.Object> ();
|
||||
|
||||
if (weaveObjs.Count > 0) {
|
||||
_rootWeave = new Weave (weaveObjs, 0);
|
||||
finalContent.Add (_rootWeave);
|
||||
}
|
||||
|
||||
if (subFlowObjs.Count > 0) {
|
||||
finalContent.AddRange (subFlowObjs);
|
||||
}
|
||||
|
||||
return finalContent;
|
||||
}
|
||||
|
||||
protected virtual void PreProcessTopLevelObjects(List<Parsed.Object> topLevelObjects)
|
||||
{
|
||||
// empty by default, used by Story to process included file references
|
||||
}
|
||||
|
||||
public struct VariableResolveResult
|
||||
{
|
||||
public bool found;
|
||||
public bool isGlobal;
|
||||
public bool isArgument;
|
||||
public bool isTemporary;
|
||||
public FlowBase ownerFlow;
|
||||
}
|
||||
|
||||
public VariableResolveResult ResolveVariableWithName(string varName, Parsed.Object fromNode)
|
||||
{
|
||||
var result = new VariableResolveResult ();
|
||||
|
||||
// Search in the stitch / knot that owns the node first
|
||||
var ownerFlow = fromNode == null ? this : fromNode.ClosestFlowBase ();
|
||||
|
||||
// Argument
|
||||
if (ownerFlow.arguments != null ) {
|
||||
foreach (var arg in ownerFlow.arguments) {
|
||||
if (arg.identifier.name.Equals (varName)) {
|
||||
result.found = true;
|
||||
result.isArgument = true;
|
||||
result.ownerFlow = ownerFlow;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temp
|
||||
var story = this.story; // optimisation
|
||||
if (ownerFlow != story && ownerFlow.variableDeclarations.ContainsKey (varName)) {
|
||||
result.found = true;
|
||||
result.ownerFlow = ownerFlow;
|
||||
result.isTemporary = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Global
|
||||
if (story.variableDeclarations.ContainsKey (varName)) {
|
||||
result.found = true;
|
||||
result.ownerFlow = story;
|
||||
result.isGlobal = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.found = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void TryAddNewVariableDeclaration(VariableAssignment varDecl)
|
||||
{
|
||||
var varName = varDecl.variableName;
|
||||
if (variableDeclarations.ContainsKey (varName)) {
|
||||
|
||||
var prevDeclError = "";
|
||||
var debugMetadata = variableDeclarations [varName].debugMetadata;
|
||||
if (debugMetadata != null) {
|
||||
prevDeclError = " ("+variableDeclarations [varName].debugMetadata+")";
|
||||
}
|
||||
Error("found declaration variable '"+varName+"' that was already declared"+prevDeclError, varDecl, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
variableDeclarations [varDecl.variableName] = varDecl;
|
||||
}
|
||||
|
||||
public void ResolveWeavePointNaming ()
|
||||
{
|
||||
// Find all weave points and organise them by name ready for
|
||||
// diverting. Also detect naming collisions.
|
||||
if( _rootWeave )
|
||||
_rootWeave.ResolveWeavePointNaming ();
|
||||
|
||||
if (_subFlowsByName != null) {
|
||||
foreach (var namedSubFlow in _subFlowsByName) {
|
||||
namedSubFlow.Value.ResolveWeavePointNaming ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Runtime.Object GenerateRuntimeObject ()
|
||||
{
|
||||
Return foundReturn = null;
|
||||
if (isFunction) {
|
||||
CheckForDisallowedFunctionFlowControl ();
|
||||
}
|
||||
|
||||
// Non-functon: Make sure knots and stitches don't attempt to use Return statement
|
||||
else if( flowLevel == FlowLevel.Knot || flowLevel == FlowLevel.Stitch ) {
|
||||
foundReturn = Find<Return> ();
|
||||
if (foundReturn != null) {
|
||||
Error ("Return statements can only be used in knots that are declared as functions: == function " + this.identifier + " ==", foundReturn);
|
||||
}
|
||||
}
|
||||
|
||||
var container = new Runtime.Container ();
|
||||
container.name = identifier?.name;
|
||||
|
||||
if( this.story.countAllVisits ) {
|
||||
container.visitsShouldBeCounted = true;
|
||||
}
|
||||
|
||||
GenerateArgumentVariableAssignments (container);
|
||||
|
||||
// Run through content defined for this knot/stitch:
|
||||
// - First of all, any initial content before a sub-stitch
|
||||
// or any weave content is added to the main content container
|
||||
// - The first inner knot/stitch is automatically entered, while
|
||||
// the others are only accessible by an explicit divert
|
||||
// - The exception to this rule is if the knot/stitch takes
|
||||
// parameters, in which case it can't be auto-entered.
|
||||
// - Any Choices and Gathers (i.e. IWeavePoint) found are
|
||||
// processsed by GenerateFlowContent.
|
||||
int contentIdx = 0;
|
||||
while (content != null && contentIdx < content.Count) {
|
||||
|
||||
Parsed.Object obj = content [contentIdx];
|
||||
|
||||
// Inner knots and stitches
|
||||
if (obj is FlowBase) {
|
||||
|
||||
var childFlow = (FlowBase)obj;
|
||||
|
||||
var childFlowRuntime = childFlow.runtimeObject;
|
||||
|
||||
// First inner stitch - automatically step into it
|
||||
// 20/09/2016 - let's not auto step into knots
|
||||
if (contentIdx == 0 && !childFlow.hasParameters
|
||||
&& this.flowLevel == FlowLevel.Knot) {
|
||||
_startingSubFlowDivert = new Runtime.Divert ();
|
||||
container.AddContent(_startingSubFlowDivert);
|
||||
_startingSubFlowRuntime = childFlowRuntime;
|
||||
}
|
||||
|
||||
// Check for duplicate knots/stitches with same name
|
||||
var namedChild = (Runtime.INamedContent)childFlowRuntime;
|
||||
Runtime.INamedContent existingChild = null;
|
||||
if (container.namedContent.TryGetValue(namedChild.name, out existingChild) ) {
|
||||
var errorMsg = string.Format ("{0} already contains flow named '{1}' (at {2})",
|
||||
this.GetType().Name,
|
||||
namedChild.name,
|
||||
(existingChild as Runtime.Object).debugMetadata);
|
||||
|
||||
Error (errorMsg, childFlow);
|
||||
}
|
||||
|
||||
container.AddToNamedContentOnly (namedChild);
|
||||
}
|
||||
|
||||
// Other content (including entire Weaves that were grouped in the constructor)
|
||||
// At the time of writing, all FlowBases have a maximum of one piece of "other content"
|
||||
// and it's always the root Weave
|
||||
else {
|
||||
container.AddContent (obj.runtimeObject);
|
||||
}
|
||||
|
||||
contentIdx++;
|
||||
}
|
||||
|
||||
// CHECK FOR FINAL LOOSE ENDS!
|
||||
// Notes:
|
||||
// - Functions don't need to terminate - they just implicitly return
|
||||
// - If return statement was found, don't continue finding warnings for missing control flow,
|
||||
// since it's likely that a return statement has been used instead of a ->-> or something,
|
||||
// or the writer failed to mark the knot as a function.
|
||||
// - _rootWeave may be null if it's a knot that only has stitches
|
||||
if (flowLevel != FlowLevel.Story && !this.isFunction && _rootWeave != null && foundReturn == null) {
|
||||
_rootWeave.ValidateTermination (WarningInTermination);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
void GenerateArgumentVariableAssignments(Runtime.Container container)
|
||||
{
|
||||
if (this.arguments == null || this.arguments.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assign parameters in reverse since they'll be popped off the evaluation stack
|
||||
// No need to generate EvalStart and EvalEnd since there's nothing being pushed
|
||||
// back onto the evaluation stack.
|
||||
for (int i = arguments.Count - 1; i >= 0; --i) {
|
||||
var paramName = arguments [i].identifier?.name;
|
||||
|
||||
var assign = new Runtime.VariableAssignment (paramName, isNewDeclaration:true);
|
||||
container.AddContent (assign);
|
||||
}
|
||||
}
|
||||
|
||||
public Parsed.Object ContentWithNameAtLevel(string name, FlowLevel? level = null, bool deepSearch = false)
|
||||
{
|
||||
// Referencing self?
|
||||
if (level == this.flowLevel || level == null) {
|
||||
if (name == this.identifier?.name) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
if ( level == FlowLevel.WeavePoint || level == null ) {
|
||||
|
||||
Parsed.Object weavePointResult = null;
|
||||
|
||||
if (_rootWeave) {
|
||||
weavePointResult = (Parsed.Object)_rootWeave.WeavePointNamed (name);
|
||||
if (weavePointResult)
|
||||
return weavePointResult;
|
||||
}
|
||||
|
||||
// Stop now if we only wanted a result if it's a weave point?
|
||||
if (level == FlowLevel.WeavePoint)
|
||||
return deepSearch ? DeepSearchForAnyLevelContent(name) : null;
|
||||
}
|
||||
|
||||
// If this flow would be incapable of containing the requested level, early out
|
||||
// (e.g. asking for a Knot from a Stitch)
|
||||
if (level != null && level < this.flowLevel)
|
||||
return null;
|
||||
|
||||
FlowBase subFlow = null;
|
||||
|
||||
if (_subFlowsByName.TryGetValue (name, out subFlow)) {
|
||||
if (level == null || level == subFlow.flowLevel)
|
||||
return subFlow;
|
||||
}
|
||||
|
||||
return deepSearch ? DeepSearchForAnyLevelContent(name) : null;
|
||||
}
|
||||
|
||||
Parsed.Object DeepSearchForAnyLevelContent(string name)
|
||||
{
|
||||
var weaveResultSelf = ContentWithNameAtLevel (name, level:FlowLevel.WeavePoint, deepSearch: false);
|
||||
if (weaveResultSelf) {
|
||||
return weaveResultSelf;
|
||||
}
|
||||
|
||||
foreach (var subFlowNamePair in _subFlowsByName) {
|
||||
var subFlow = subFlowNamePair.Value;
|
||||
var deepResult = subFlow.ContentWithNameAtLevel (name, level:null, deepSearch: true);
|
||||
if (deepResult)
|
||||
return deepResult;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void ResolveReferences (Story context)
|
||||
{
|
||||
if (_startingSubFlowDivert) {
|
||||
_startingSubFlowDivert.targetPath = _startingSubFlowRuntime.path;
|
||||
}
|
||||
|
||||
base.ResolveReferences(context);
|
||||
|
||||
// Check validity of parameter names
|
||||
if (arguments != null) {
|
||||
|
||||
foreach (var arg in arguments)
|
||||
context.CheckForNamingCollisions (this, arg.identifier, Story.SymbolType.Arg, "argument");
|
||||
|
||||
// Separately, check for duplicate arugment names, since they aren't Parsed.Objects,
|
||||
// so have to be checked independently.
|
||||
for (int i = 0; i < arguments.Count; i++) {
|
||||
for (int j = i + 1; j < arguments.Count; j++) {
|
||||
if (arguments [i].identifier?.name == arguments [j].identifier?.name) {
|
||||
Error ("Multiple arguments with the same name: '" + arguments [i].identifier + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check naming collisions for knots and stitches
|
||||
if (flowLevel != FlowLevel.Story) {
|
||||
// Weave points aren't FlowBases, so this will only be knot or stitch
|
||||
var symbolType = flowLevel == FlowLevel.Knot ? Story.SymbolType.Knot : Story.SymbolType.SubFlowAndWeave;
|
||||
context.CheckForNamingCollisions (this, identifier, symbolType);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckForDisallowedFunctionFlowControl()
|
||||
{
|
||||
if (!(this is Knot)) {
|
||||
Error ("Functions cannot be stitches - i.e. they should be defined as '== function myFunc ==' rather than public to another knot.");
|
||||
}
|
||||
|
||||
// Not allowed sub-flows
|
||||
foreach (var subFlowAndName in _subFlowsByName) {
|
||||
var name = subFlowAndName.Key;
|
||||
var subFlow = subFlowAndName.Value;
|
||||
Error ("Functions may not contain stitches, but saw '"+name+"' within the function '"+this.identifier+"'", subFlow);
|
||||
}
|
||||
|
||||
var allDiverts = _rootWeave.FindAll<Divert> ();
|
||||
foreach (var divert in allDiverts) {
|
||||
if( !divert.isFunctionCall && !(divert.parent is DivertTarget) )
|
||||
Error ("Functions may not contain diverts, but saw '"+divert.ToString()+"'", divert);
|
||||
}
|
||||
|
||||
var allChoices = _rootWeave.FindAll<Choice> ();
|
||||
foreach (var choice in allChoices) {
|
||||
Error ("Functions may not contain choices, but saw '"+choice.ToString()+"'", choice);
|
||||
}
|
||||
}
|
||||
|
||||
void WarningInTermination(Parsed.Object terminatingObject)
|
||||
{
|
||||
string message = "Apparent loose end exists where the flow runs out. Do you need a '-> DONE' statement, choice or divert?";
|
||||
if (terminatingObject.parent == _rootWeave && _firstChildFlow) {
|
||||
message = message + " Note that if you intend to enter '"+_firstChildFlow.identifier+"' next, you need to divert to it explicitly.";
|
||||
}
|
||||
|
||||
var terminatingDivert = terminatingObject as Divert;
|
||||
if (terminatingDivert && terminatingDivert.isTunnel) {
|
||||
message = message + " When final tunnel to '"+terminatingDivert.target+" ->' returns it won't have anywhere to go.";
|
||||
}
|
||||
|
||||
Warning (message, terminatingObject);
|
||||
}
|
||||
|
||||
protected Dictionary<string, FlowBase> subFlowsByName {
|
||||
get {
|
||||
return _subFlowsByName;
|
||||
}
|
||||
}
|
||||
|
||||
public override string typeName {
|
||||
get {
|
||||
if (isFunction) return "Function";
|
||||
else return flowLevel.ToString ();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return typeName+" '" + identifier + "'";
|
||||
}
|
||||
|
||||
Weave _rootWeave;
|
||||
Dictionary<string, FlowBase> _subFlowsByName;
|
||||
Runtime.Divert _startingSubFlowDivert;
|
||||
Runtime.Object _startingSubFlowRuntime;
|
||||
FlowBase _firstChildFlow;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user