mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 02:24:28 +11:00
367 lines
11 KiB
C#
367 lines
11 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.ComponentModel;
|
|
|
|
namespace Ink.Runtime
|
|
{
|
|
public class Container : Runtime.Object, INamedContent
|
|
{
|
|
public string name { get; set; }
|
|
|
|
public List<Runtime.Object> content {
|
|
get {
|
|
return _content;
|
|
}
|
|
set {
|
|
AddContent (value);
|
|
}
|
|
}
|
|
List<Runtime.Object> _content;
|
|
|
|
public Dictionary<string, INamedContent> namedContent { get; set; }
|
|
|
|
public Dictionary<string, Runtime.Object> namedOnlyContent {
|
|
get {
|
|
var namedOnlyContentDict = new Dictionary<string, Runtime.Object>();
|
|
foreach (var kvPair in namedContent) {
|
|
namedOnlyContentDict [kvPair.Key] = (Runtime.Object)kvPair.Value;
|
|
}
|
|
|
|
foreach (var c in content) {
|
|
var named = c as INamedContent;
|
|
if (named != null && named.hasValidName) {
|
|
namedOnlyContentDict.Remove (named.name);
|
|
}
|
|
}
|
|
|
|
if (namedOnlyContentDict.Count == 0)
|
|
namedOnlyContentDict = null;
|
|
|
|
return namedOnlyContentDict;
|
|
}
|
|
set {
|
|
var existingNamedOnly = namedOnlyContent;
|
|
if (existingNamedOnly != null) {
|
|
foreach (var kvPair in existingNamedOnly) {
|
|
namedContent.Remove (kvPair.Key);
|
|
}
|
|
}
|
|
|
|
if (value == null)
|
|
return;
|
|
|
|
foreach (var kvPair in value) {
|
|
var named = kvPair.Value as INamedContent;
|
|
if( named != null )
|
|
AddToNamedContentOnly (named);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool visitsShouldBeCounted { get; set; }
|
|
public bool turnIndexShouldBeCounted { get; set; }
|
|
public bool countingAtStartOnly { get; set; }
|
|
|
|
[Flags]
|
|
public enum CountFlags
|
|
{
|
|
Visits = 1,
|
|
Turns = 2,
|
|
CountStartOnly = 4
|
|
}
|
|
|
|
public int countFlags
|
|
{
|
|
get {
|
|
CountFlags flags = 0;
|
|
if (visitsShouldBeCounted) flags |= CountFlags.Visits;
|
|
if (turnIndexShouldBeCounted) flags |= CountFlags.Turns;
|
|
if (countingAtStartOnly) flags |= CountFlags.CountStartOnly;
|
|
|
|
// If we're only storing CountStartOnly, it serves no purpose,
|
|
// since it's dependent on the other two to be used at all.
|
|
// (e.g. for setting the fact that *if* a gather or choice's
|
|
// content is counted, then is should only be counter at the start)
|
|
// So this is just an optimisation for storage.
|
|
if (flags == CountFlags.CountStartOnly) {
|
|
flags = 0;
|
|
}
|
|
|
|
return (int)flags;
|
|
}
|
|
set {
|
|
var flag = (CountFlags)value;
|
|
if ((flag & CountFlags.Visits) > 0) visitsShouldBeCounted = true;
|
|
if ((flag & CountFlags.Turns) > 0) turnIndexShouldBeCounted = true;
|
|
if ((flag & CountFlags.CountStartOnly) > 0) countingAtStartOnly = true;
|
|
}
|
|
}
|
|
|
|
public bool hasValidName
|
|
{
|
|
get { return name != null && name.Length > 0; }
|
|
}
|
|
|
|
public Path pathToFirstLeafContent
|
|
{
|
|
get {
|
|
if( _pathToFirstLeafContent == null )
|
|
_pathToFirstLeafContent = path.PathByAppendingPath (internalPathToFirstLeafContent);
|
|
|
|
return _pathToFirstLeafContent;
|
|
}
|
|
}
|
|
Path _pathToFirstLeafContent;
|
|
|
|
Path internalPathToFirstLeafContent
|
|
{
|
|
get {
|
|
var components = new List<Path.Component>();
|
|
var container = this;
|
|
while (container != null) {
|
|
if (container.content.Count > 0) {
|
|
components.Add (new Path.Component (0));
|
|
container = container.content [0] as Container;
|
|
}
|
|
}
|
|
return new Path(components);
|
|
}
|
|
}
|
|
|
|
public Container ()
|
|
{
|
|
_content = new List<Runtime.Object> ();
|
|
namedContent = new Dictionary<string, INamedContent> ();
|
|
}
|
|
|
|
public void AddContent(Runtime.Object contentObj)
|
|
{
|
|
content.Add (contentObj);
|
|
|
|
if (contentObj.parent) {
|
|
throw new System.Exception ("content is already in " + contentObj.parent);
|
|
}
|
|
|
|
contentObj.parent = this;
|
|
|
|
TryAddNamedContent (contentObj);
|
|
}
|
|
|
|
public void AddContent(IList<Runtime.Object> contentList)
|
|
{
|
|
foreach (var c in contentList) {
|
|
AddContent (c);
|
|
}
|
|
}
|
|
|
|
public void InsertContent(Runtime.Object contentObj, int index)
|
|
{
|
|
content.Insert (index, contentObj);
|
|
|
|
if (contentObj.parent) {
|
|
throw new System.Exception ("content is already in " + contentObj.parent);
|
|
}
|
|
|
|
contentObj.parent = this;
|
|
|
|
TryAddNamedContent (contentObj);
|
|
}
|
|
|
|
public void TryAddNamedContent(Runtime.Object contentObj)
|
|
{
|
|
var namedContentObj = contentObj as INamedContent;
|
|
if (namedContentObj != null && namedContentObj.hasValidName) {
|
|
AddToNamedContentOnly (namedContentObj);
|
|
}
|
|
}
|
|
|
|
public void AddToNamedContentOnly(INamedContent namedContentObj)
|
|
{
|
|
Debug.Assert (namedContentObj is Runtime.Object, "Can only add Runtime.Objects to a Runtime.Container");
|
|
var runtimeObj = (Runtime.Object)namedContentObj;
|
|
runtimeObj.parent = this;
|
|
|
|
namedContent [namedContentObj.name] = namedContentObj;
|
|
}
|
|
|
|
public void AddContentsOfContainer(Container otherContainer)
|
|
{
|
|
content.AddRange (otherContainer.content);
|
|
foreach (var obj in otherContainer.content) {
|
|
obj.parent = this;
|
|
TryAddNamedContent (obj);
|
|
}
|
|
}
|
|
|
|
protected Runtime.Object ContentWithPathComponent(Path.Component component)
|
|
{
|
|
if (component.isIndex) {
|
|
|
|
if (component.index >= 0 && component.index < content.Count) {
|
|
return content [component.index];
|
|
}
|
|
|
|
// When path is out of range, quietly return nil
|
|
// (useful as we step/increment forwards through content)
|
|
else {
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
else if (component.isParent) {
|
|
return this.parent;
|
|
}
|
|
|
|
else {
|
|
INamedContent foundContent = null;
|
|
if (namedContent.TryGetValue (component.name, out foundContent)) {
|
|
return (Runtime.Object)foundContent;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public SearchResult ContentAtPath(Path path, int partialPathStart = 0, int partialPathLength = -1)
|
|
{
|
|
if (partialPathLength == -1)
|
|
partialPathLength = path.length;
|
|
|
|
var result = new SearchResult ();
|
|
result.approximate = false;
|
|
|
|
Container currentContainer = this;
|
|
Runtime.Object currentObj = this;
|
|
|
|
for (int i = partialPathStart; i < partialPathLength; ++i) {
|
|
var comp = path.GetComponent(i);
|
|
|
|
// Path component was wrong type
|
|
if (currentContainer == null) {
|
|
result.approximate = true;
|
|
break;
|
|
}
|
|
|
|
var foundObj = currentContainer.ContentWithPathComponent(comp);
|
|
|
|
// Couldn't resolve entire path?
|
|
if (foundObj == null) {
|
|
result.approximate = true;
|
|
break;
|
|
}
|
|
|
|
currentObj = foundObj;
|
|
currentContainer = foundObj as Container;
|
|
}
|
|
|
|
result.obj = currentObj;
|
|
|
|
return result;
|
|
}
|
|
|
|
public void BuildStringOfHierarchy(StringBuilder sb, int indentation, Runtime.Object pointedObj)
|
|
{
|
|
Action appendIndentation = () => {
|
|
const int spacesPerIndent = 4;
|
|
for(int i=0; i<spacesPerIndent*indentation;++i) {
|
|
sb.Append(" ");
|
|
}
|
|
};
|
|
|
|
appendIndentation ();
|
|
sb.Append("[");
|
|
|
|
if (this.hasValidName) {
|
|
sb.AppendFormat (" ({0})", this.name);
|
|
}
|
|
|
|
if (this == pointedObj) {
|
|
sb.Append (" <---");
|
|
}
|
|
|
|
sb.AppendLine ();
|
|
|
|
indentation++;
|
|
|
|
for (int i=0; i<content.Count; ++i) {
|
|
|
|
var obj = content [i];
|
|
|
|
if (obj is Container) {
|
|
|
|
var container = (Container)obj;
|
|
|
|
container.BuildStringOfHierarchy (sb, indentation, pointedObj);
|
|
|
|
} else {
|
|
appendIndentation ();
|
|
if (obj is StringValue) {
|
|
sb.Append ("\"");
|
|
sb.Append (obj.ToString ().Replace ("\n", "\\n"));
|
|
sb.Append ("\"");
|
|
} else {
|
|
sb.Append (obj.ToString ());
|
|
}
|
|
}
|
|
|
|
if (i != content.Count - 1) {
|
|
sb.Append (",");
|
|
}
|
|
|
|
if ( !(obj is Container) && obj == pointedObj ) {
|
|
sb.Append (" <---");
|
|
}
|
|
|
|
sb.AppendLine ();
|
|
}
|
|
|
|
|
|
var onlyNamed = new Dictionary<string, INamedContent> ();
|
|
|
|
foreach (var objKV in namedContent) {
|
|
if (content.Contains ((Runtime.Object)objKV.Value)) {
|
|
continue;
|
|
} else {
|
|
onlyNamed.Add (objKV.Key, objKV.Value);
|
|
}
|
|
}
|
|
|
|
if (onlyNamed.Count > 0) {
|
|
appendIndentation ();
|
|
sb.AppendLine ("-- named: --");
|
|
|
|
foreach (var objKV in onlyNamed) {
|
|
|
|
Debug.Assert (objKV.Value is Container, "Can only print out named Containers");
|
|
var container = (Container)objKV.Value;
|
|
container.BuildStringOfHierarchy (sb, indentation, pointedObj);
|
|
|
|
sb.AppendLine ();
|
|
|
|
}
|
|
}
|
|
|
|
|
|
indentation--;
|
|
|
|
appendIndentation ();
|
|
sb.Append ("]");
|
|
}
|
|
|
|
public virtual string BuildStringOfHierarchy()
|
|
{
|
|
var sb = new StringBuilder ();
|
|
|
|
BuildStringOfHierarchy (sb, 0, null);
|
|
|
|
return sb.ToString ();
|
|
}
|
|
|
|
}
|
|
}
|
|
|