- using System.Collections.Generic;
- using System.Linq;
- namespace Ink.Parsed
- {
- public class VariableReference : Expression
- {
- // - Normal variables have a single item in their "path"
- // - Knot/stitch names for read counts are actual dot-separated paths
- // (though this isn't actually used at time of writing)
- // - List names are dot separated: listName.itemName (or just itemName)
- public string name { get; private set; }
- public Identifier identifier {
- get {
- // Merging the list of identifiers into a single identifier.
- // Debug metadata is also merged.
- if (pathIdentifiers == null || pathIdentifiers.Count == 0) {
- return null;
- }
- if( _singleIdentifier == null ) {
- var name = string.Join (".", path.ToArray());
- var firstDebugMetadata = pathIdentifiers.First().debugMetadata;
- var debugMetadata = pathIdentifiers.Aggregate(firstDebugMetadata, (acc, id) => acc.Merge(id.debugMetadata));
- _singleIdentifier = new Identifier { name = name, debugMetadata = debugMetadata };
- }
-
- return _singleIdentifier;
- }
- }
- Identifier _singleIdentifier;
- public List<Identifier> pathIdentifiers;
- public List<string> path { get; private set; }
- // Only known after GenerateIntoContainer has run
- public bool isConstantReference;
- public bool isListItemReference;
- public Runtime.VariableReference runtimeVarRef { get { return _runtimeVarRef; } }
- public VariableReference (List<Identifier> pathIdentifiers)
- {
- this.pathIdentifiers = pathIdentifiers;
- this.path = pathIdentifiers.Select(id => id?.name).ToList();
- this.name = string.Join (".", pathIdentifiers);
- }
- public override void GenerateIntoContainer (Runtime.Container container)
- {
- Expression constantValue = null;
- // If it's a constant reference, just generate the literal expression value
- // It's okay to access the constants at code generation time, since the
- // first thing the ExportRuntime function does it search for all the constants
- // in the story hierarchy, so they're all available.
- if ( story.constants.TryGetValue (name, out constantValue) ) {
- constantValue.GenerateConstantIntoContainer (container);
- isConstantReference = true;
- return;
- }
- _runtimeVarRef = new Runtime.VariableReference (name);
- // List item reference?
- // Path might be to a list (listName.listItemName or just listItemName)
- if (path.Count == 1 || path.Count == 2) {
- string listItemName = null;
- string listName = null;
- if (path.Count == 1) listItemName = path [0];
- else {
- listName = path [0];
- listItemName = path [1];
- }
- var listItem = story.ResolveListItem (listName, listItemName, this);
- if (listItem) {
- isListItemReference = true;
- }
- }
- container.AddContent (_runtimeVarRef);
- }
- public override void ResolveReferences (Story context)
- {
- base.ResolveReferences (context);
- // Work is already done if it's a constant or list item reference
- if (isConstantReference || isListItemReference) {
- return;
- }
- // Is it a read count?
- var parsedPath = new Path (pathIdentifiers);
- Parsed.Object targetForCount = parsedPath.ResolveFromContext (this);
- if (targetForCount) {
- targetForCount.containerForCounting.visitsShouldBeCounted = true;
- // If this is an argument to a function that wants a variable to be
- // passed by reference, then the Parsed.Divert will have generated a
- // Runtime.VariablePointerValue instead of allowing this object
- // to generate its RuntimeVariableReference. This only happens under
- // error condition since we shouldn't be passing a read count by
- // reference, but we don't want it to crash!
- if (_runtimeVarRef == null) return;
- _runtimeVarRef.pathForCount = targetForCount.runtimePath;
- _runtimeVarRef.name = null;
- // Check for very specific writer error: getting read count and
- // printing it as content rather than as a piece of logic
- // e.g. Writing {myFunc} instead of {myFunc()}
- var targetFlow = targetForCount as FlowBase;
- if (targetFlow && targetFlow.isFunction) {
- // Is parent context content rather than logic?
- if ( parent is Weave || parent is ContentList || parent is FlowBase) {
- Warning ("'" + targetFlow.identifier + "' being used as read count rather than being called as function. Perhaps you intended to write " + targetFlow.name + "()");
- }
- }
- return;
- }
- // Couldn't find this multi-part path at all, whether as a divert
- // target or as a list item reference.
- if (path.Count > 1) {
- var errorMsg = "Could not find target for read count: " + parsedPath;
- if (path.Count <= 2)
- errorMsg += ", or couldn't find list item with the name " + string.Join (",", path.ToArray());
- Error (errorMsg);
- return;
- }
- if (!context.ResolveVariableWithName (this.name, fromNode: this).found) {
- Error("Unresolved variable: "+this.ToString(), this);
- }
- }
- public override string ToString ()
- {
- return string.Join(".", path.ToArray());
- }
- Runtime.VariableReference _runtimeVarRef;
- }
- }