Files
Mementos/Unity/Alternate Genre Jam/Assets/Ink/InkLibs/InkCompiler/ParsedHierarchy/Object.cs
2021-06-30 21:39:19 +10:00

364 lines
10 KiB
C#

using System.Collections.Generic;
using System.Text;
namespace Ink.Parsed
{
public abstract class Object
{
public Runtime.DebugMetadata debugMetadata {
get {
if (_debugMetadata == null) {
if (parent) {
return parent.debugMetadata;
}
}
return _debugMetadata;
}
set {
_debugMetadata = value;
}
}
private Runtime.DebugMetadata _debugMetadata;
public bool hasOwnDebugMetadata {
get {
return _debugMetadata != null;
}
}
public virtual string typeName {
get {
return GetType().Name;
}
}
public Parsed.Object parent { get; set; }
public List<Parsed.Object> content { get; protected set; }
public Parsed.Story story {
get {
Parsed.Object ancestor = this;
while (ancestor.parent) {
ancestor = ancestor.parent;
}
return ancestor as Parsed.Story;
}
}
private Runtime.Object _runtimeObject;
public Runtime.Object runtimeObject
{
get {
if (_runtimeObject == null) {
_runtimeObject = GenerateRuntimeObject ();
if( _runtimeObject )
_runtimeObject.debugMetadata = debugMetadata;
}
return _runtimeObject;
}
set {
_runtimeObject = value;
}
}
// virtual so that certian object types can return a different
// path than just the path to the main runtimeObject.
// e.g. a Choice returns a path to its content rather than
// its outer container.
public virtual Runtime.Path runtimePath
{
get {
return runtimeObject.path;
}
}
// When counting visits and turns since, different object
// types may have different containers that needs to be counted.
// For most it'll just be the object's main runtime object,
// but for e.g. choices, it'll be the target container.
public virtual Runtime.Container containerForCounting
{
get {
return this.runtimeObject as Runtime.Container;
}
}
public Parsed.Path PathRelativeTo(Parsed.Object otherObj)
{
var ownAncestry = ancestry;
var otherAncestry = otherObj.ancestry;
Parsed.Object highestCommonAncestor = null;
int minLength = System.Math.Min (ownAncestry.Count, otherAncestry.Count);
for (int i = 0; i < minLength; ++i) {
var a1 = ancestry [i];
var a2 = otherAncestry [i];
if (a1 == a2)
highestCommonAncestor = a1;
else
break;
}
FlowBase commonFlowAncestor = highestCommonAncestor as FlowBase;
if (commonFlowAncestor == null)
commonFlowAncestor = highestCommonAncestor.ClosestFlowBase ();
var pathComponents = new List<Identifier> ();
bool hasWeavePoint = false;
FlowLevel baseFlow = FlowLevel.WeavePoint;
var ancestor = this;
while(ancestor && (ancestor != commonFlowAncestor) && !(ancestor is Story)) {
if (ancestor == commonFlowAncestor)
break;
if (!hasWeavePoint) {
var weavePointAncestor = ancestor as IWeavePoint;
if (weavePointAncestor != null && weavePointAncestor.identifier != null) {
pathComponents.Add (weavePointAncestor.identifier);
hasWeavePoint = true;
continue;
}
}
var flowAncestor = ancestor as FlowBase;
if (flowAncestor) {
pathComponents.Add (flowAncestor.identifier);
baseFlow = flowAncestor.flowLevel;
}
ancestor = ancestor.parent;
}
pathComponents.Reverse ();
if (pathComponents.Count > 0) {
return new Path (baseFlow, pathComponents);
}
return null;
}
public List<Parsed.Object> ancestry
{
get {
var result = new List<Parsed.Object> ();
var ancestor = this.parent;
while(ancestor) {
result.Add (ancestor);
ancestor = ancestor.parent;
}
result.Reverse ();
return result;
}
}
public string descriptionOfScope
{
get {
var locationNames = new List<string> ();
Parsed.Object ancestor = this;
while (ancestor) {
var ancestorFlow = ancestor as FlowBase;
if (ancestorFlow && ancestorFlow.identifier != null) {
locationNames.Add ("'" + ancestorFlow.identifier + "'");
}
ancestor = ancestor.parent;
}
var scopeSB = new StringBuilder ();
if (locationNames.Count > 0) {
var locationsListStr = string.Join (", ", locationNames.ToArray());
scopeSB.Append (locationsListStr);
scopeSB.Append (" and ");
}
scopeSB.Append ("at top scope");
return scopeSB.ToString ();
}
}
// Return the object so that method can be chained easily
public T AddContent<T>(T subContent) where T : Parsed.Object
{
if (content == null) {
content = new List<Parsed.Object> ();
}
// Make resilient to content not existing, which can happen
// in the case of parse errors where we've already reported
// an error but still want a valid structure so we can
// carry on parsing.
if( subContent ) {
subContent.parent = this;
content.Add(subContent);
}
return subContent;
}
public void AddContent<T>(List<T> listContent) where T : Parsed.Object
{
foreach (var obj in listContent) {
AddContent (obj);
}
}
public T InsertContent<T>(int index, T subContent) where T : Parsed.Object
{
if (content == null) {
content = new List<Parsed.Object> ();
}
subContent.parent = this;
content.Insert (index, subContent);
return subContent;
}
public delegate bool FindQueryFunc<T>(T obj);
public T Find<T>(FindQueryFunc<T> queryFunc = null) where T : class
{
var tObj = this as T;
if (tObj != null && (queryFunc == null || queryFunc (tObj) == true)) {
return tObj;
}
if (content == null)
return null;
foreach (var obj in content) {
var nestedResult = obj.Find (queryFunc);
if (nestedResult != null)
return nestedResult;
}
return null;
}
public List<T> FindAll<T>(FindQueryFunc<T> queryFunc = null) where T : class
{
var found = new List<T> ();
FindAll (queryFunc, found);
return found;
}
void FindAll<T>(FindQueryFunc<T> queryFunc, List<T> foundSoFar) where T : class
{
var tObj = this as T;
if (tObj != null && (queryFunc == null || queryFunc (tObj) == true)) {
foundSoFar.Add (tObj);
}
if (content == null)
return;
foreach (var obj in content) {
obj.FindAll (queryFunc, foundSoFar);
}
}
public abstract Runtime.Object GenerateRuntimeObject ();
public virtual void ResolveReferences(Story context)
{
if (content != null) {
foreach(var obj in content) {
obj.ResolveReferences (context);
}
}
}
public FlowBase ClosestFlowBase()
{
var ancestor = this.parent;
while (ancestor) {
if (ancestor is FlowBase) {
return (FlowBase)ancestor;
}
ancestor = ancestor.parent;
}
return null;
}
public virtual void Error(string message, Parsed.Object source = null, bool isWarning = false)
{
if (source == null) {
source = this;
}
// Only allow a single parsed object to have a single error *directly* associated with it
if (source._alreadyHadError && !isWarning) {
return;
}
if (source._alreadyHadWarning && isWarning) {
return;
}
if (this.parent) {
this.parent.Error (message, source, isWarning);
} else {
throw new System.Exception ("No parent object to send error to: "+message);
}
if (isWarning) {
source._alreadyHadWarning = true;
} else {
source._alreadyHadError = true;
}
}
public void Warning(string message, Parsed.Object source = null)
{
Error (message, source, isWarning: true);
}
// Allow implicit conversion to bool so you don't have to do:
// if( myObj != null ) ...
public static implicit operator bool (Object obj)
{
var isNull = object.ReferenceEquals (obj, null);
return !isNull;
}
public static bool operator ==(Object a, Object b)
{
return object.ReferenceEquals (a, b);
}
public static bool operator !=(Object a, Object b)
{
return !(a == b);
}
public override bool Equals (object obj)
{
return object.ReferenceEquals (obj, this);
}
public override int GetHashCode ()
{
return base.GetHashCode ();
}
bool _alreadyHadError;
bool _alreadyHadWarning;
}
}