DoorwayCleanup is now obselete
Created new scripting system Added toggle for EventCallbackScenario for DevDebug window
This commit is contained in:
parent
4af194e0f4
commit
25530ebbb3
@ -11,6 +11,7 @@ using UnityEngine;
|
|||||||
namespace DunGenPlus.Components {
|
namespace DunGenPlus.Components {
|
||||||
public class DoorwayCleanup : MonoBehaviour, IDungeonCompleteReceiver {
|
public class DoorwayCleanup : MonoBehaviour, IDungeonCompleteReceiver {
|
||||||
|
|
||||||
|
[Header("OBSOLUTE. Please use DoorwayScriptingParent")]
|
||||||
[Header("Doorway References")]
|
[Header("Doorway References")]
|
||||||
[Tooltip("The doorway reference.")]
|
[Tooltip("The doorway reference.")]
|
||||||
public Doorway doorway;
|
public Doorway doorway;
|
||||||
|
@ -6,6 +6,8 @@ using System.Threading.Tasks;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||||
|
|
||||||
|
[Obsolete("Please use DoorwayScriptingParent")]
|
||||||
public class DCSConnectorBlockerSpawnedPrefab : DoorwayCleanupScript {
|
public class DCSConnectorBlockerSpawnedPrefab : DoorwayCleanupScript {
|
||||||
|
|
||||||
public enum Action { SwitchToConnector, SwitchToBlocker };
|
public enum Action { SwitchToConnector, SwitchToBlocker };
|
||||||
|
@ -6,17 +6,20 @@ using System.Threading.Tasks;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||||
|
|
||||||
|
[Obsolete("Please use DoorwayScriptingParent")]
|
||||||
public class DCSRemoveDoorwayConnectedDoorway : DoorwayCleanupScriptDoorwayCompare {
|
public class DCSRemoveDoorwayConnectedDoorway : DoorwayCleanupScriptDoorwayCompare {
|
||||||
|
|
||||||
[Header("Removes Doorway Gameobject\nif the neighboring doorway's priority matches the operation comparison")]
|
[Header("Removes Doorway Gameobject\nif the neighboring doorway's priority matches the operation comparison")]
|
||||||
[Header("Operation Comparison")]
|
[Header("Operation Comparison")]
|
||||||
public int doorwayPriority;
|
public int doorwayPriority;
|
||||||
|
public int doorwayPriorityB;
|
||||||
public Operation operation = Operation.Equal;
|
public Operation operation = Operation.Equal;
|
||||||
|
|
||||||
public override void Cleanup(DoorwayCleanup parent) {
|
public override void Cleanup(DoorwayCleanup parent) {
|
||||||
var doorway = parent.doorway;
|
var doorway = parent.doorway;
|
||||||
if (doorway.connectedDoorway == null) return;
|
if (doorway.connectedDoorway == null) return;
|
||||||
var result = GetOperation(operation).Invoke(doorway.connectedDoorway, doorwayPriority);
|
var result = GetOperation(operation).Invoke(doorway.connectedDoorway, new Arguments(doorwayPriority, doorwayPriorityB));
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
parent.SwitchDoorwayGameObject(false);
|
parent.SwitchDoorwayGameObject(false);
|
||||||
|
@ -6,6 +6,8 @@ using System.Threading.Tasks;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||||
|
|
||||||
|
[Obsolete("Please use DoorwayScriptingParent")]
|
||||||
public class DCSRemoveDoorwaySpawnedPrefab : DoorwayCleanupScript {
|
public class DCSRemoveDoorwaySpawnedPrefab : DoorwayCleanupScript {
|
||||||
|
|
||||||
[Header("Removes Doorway Gameobject\nif Doorway instantiates a Connector/Blocker prefab with the target's name")]
|
[Header("Removes Doorway Gameobject\nif Doorway instantiates a Connector/Blocker prefab with the target's name")]
|
||||||
|
@ -6,12 +6,14 @@ using System.Threading.Tasks;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||||
|
[Obsolete("Please use DoorwayScriptingParent")]
|
||||||
public class DCSRemoveGameObjectsConnectedDoorway : DoorwayCleanupScriptDoorwayCompare {
|
public class DCSRemoveGameObjectsConnectedDoorway : DoorwayCleanupScriptDoorwayCompare {
|
||||||
|
|
||||||
[Header("Removes target GameObjects\nif the neighboring doorway's priority matches the operation comparison")]
|
[Header("Removes target GameObjects\nif the neighboring doorway's priority matches the operation comparison")]
|
||||||
|
|
||||||
[Header("Operation Comparison")]
|
[Header("Operation Comparison")]
|
||||||
public int doorwayPriority;
|
public int doorwayPriority;
|
||||||
|
public int doorwayPriorityB;
|
||||||
public Operation operation = Operation.Equal;
|
public Operation operation = Operation.Equal;
|
||||||
|
|
||||||
[Header("Targets")]
|
[Header("Targets")]
|
||||||
@ -20,7 +22,7 @@ namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
|||||||
public override void Cleanup(DoorwayCleanup parent) {
|
public override void Cleanup(DoorwayCleanup parent) {
|
||||||
var doorway = parent.doorway;
|
var doorway = parent.doorway;
|
||||||
if (doorway.connectedDoorway == null) return;
|
if (doorway.connectedDoorway == null) return;
|
||||||
var result = GetOperation(operation).Invoke(doorway.connectedDoorway, doorwayPriority);
|
var result = GetOperation(operation).Invoke(doorway.connectedDoorway, new Arguments(doorwayPriority, doorwayPriorityB));
|
||||||
if (result) {
|
if (result) {
|
||||||
foreach(var t in targets) t.SetActive(false);
|
foreach(var t in targets) t.SetActive(false);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||||
|
[Obsolete("Please use DoorwayScriptingParent")]
|
||||||
public abstract class DoorwayCleanupScript : MonoBehaviour {
|
public abstract class DoorwayCleanupScript : MonoBehaviour {
|
||||||
|
|
||||||
public abstract void Cleanup(DoorwayCleanup parent);
|
public abstract void Cleanup(DoorwayCleanup parent);
|
||||||
|
@ -4,13 +4,44 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||||
|
[Obsolete("Please use DoorwayScriptingParent")]
|
||||||
public abstract class DoorwayCleanupScriptDoorwayCompare : DoorwayCleanupScript {
|
public abstract class DoorwayCleanupScriptDoorwayCompare : DoorwayCleanupScript {
|
||||||
|
|
||||||
public enum Operation { Equal, NotEqual, LessThan, GreaterThan }
|
public enum Operation {
|
||||||
|
[InspectorName("Equal (target == value)")]
|
||||||
|
Equal,
|
||||||
|
[InspectorName("NotEqual (target != value)")]
|
||||||
|
NotEqual,
|
||||||
|
[InspectorName("LessThan (target < value)")]
|
||||||
|
LessThan,
|
||||||
|
[InspectorName("GreaterThan (target > value)")]
|
||||||
|
GreaterThan,
|
||||||
|
[InspectorName("LessThanEq (target <= value)")]
|
||||||
|
LessThanEq,
|
||||||
|
[InspectorName("GreaterThanEw (target >= value)")]
|
||||||
|
GreaterThanEq,
|
||||||
|
[InspectorName("Between (value < target < valueB)")]
|
||||||
|
Between,
|
||||||
|
[InspectorName("BetweenEq (value <= target <= valueB)")]
|
||||||
|
BetweenEq
|
||||||
|
}
|
||||||
|
|
||||||
public Func<Doorway, int, bool> GetOperation(Operation operation){
|
public struct Arguments{
|
||||||
|
public int parameterA;
|
||||||
|
public int parameterB;
|
||||||
|
|
||||||
|
public Arguments(int parameterA, int parameterB){
|
||||||
|
this.parameterA = parameterA;
|
||||||
|
this.parameterB = parameterB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static explicit operator Arguments((int a, int b) pair) => new Arguments(pair.a, pair.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Func<Doorway, Arguments, bool> GetOperation(Operation operation){
|
||||||
switch(operation){
|
switch(operation){
|
||||||
case Operation.Equal:
|
case Operation.Equal:
|
||||||
return EqualOperation;
|
return EqualOperation;
|
||||||
@ -20,24 +51,48 @@ namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
|||||||
return LessThanOperation;
|
return LessThanOperation;
|
||||||
case Operation.GreaterThan:
|
case Operation.GreaterThan:
|
||||||
return GreaterThanOperation;
|
return GreaterThanOperation;
|
||||||
|
case Operation.LessThanEq:
|
||||||
|
return LessThanEqualOperation;
|
||||||
|
case Operation.GreaterThanEq:
|
||||||
|
return GreaterThanEqualOperation;
|
||||||
|
case Operation.Between:
|
||||||
|
return BetweenOperation;
|
||||||
|
case Operation.BetweenEq:
|
||||||
|
return BetweenEqualOperation;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EqualOperation(Doorway other, int doorwayPriority){
|
public bool EqualOperation(Doorway other, Arguments arguments){
|
||||||
return other.DoorPrefabPriority == doorwayPriority;
|
return other.DoorPrefabPriority == arguments.parameterA;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool NotEqualOperation(Doorway other, int doorwayPriority){
|
public bool NotEqualOperation(Doorway other, Arguments arguments){
|
||||||
return other.DoorPrefabPriority != doorwayPriority;
|
return other.DoorPrefabPriority != arguments.parameterA;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LessThanOperation(Doorway other, int doorwayPriority){
|
public bool LessThanOperation(Doorway other, Arguments arguments){
|
||||||
return other.DoorPrefabPriority < doorwayPriority;
|
return other.DoorPrefabPriority < arguments.parameterA;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GreaterThanOperation(Doorway other, int doorwayPriority){
|
public bool GreaterThanOperation(Doorway other, Arguments arguments){
|
||||||
return other.DoorPrefabPriority > doorwayPriority;
|
return other.DoorPrefabPriority > arguments.parameterA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool LessThanEqualOperation(Doorway other, Arguments arguments){
|
||||||
|
return other.DoorPrefabPriority <= arguments.parameterA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GreaterThanEqualOperation(Doorway other, Arguments arguments){
|
||||||
|
return other.DoorPrefabPriority >= arguments.parameterA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BetweenOperation(Doorway other, Arguments arguments){
|
||||||
|
return arguments.parameterA < other.DoorPrefabPriority && other.DoorPrefabPriority < arguments.parameterB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BetweenEqualOperation(Doorway other, Arguments arguments){
|
||||||
|
return arguments.parameterA <= other.DoorPrefabPriority && other.DoorPrefabPriority <= arguments.parameterB;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
using DunGen;
|
||||||
|
using DunGenPlus.Managers;
|
||||||
|
using Soukoku.ExpressionParser;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace DunGenPlus.Components.Scripting {
|
||||||
|
|
||||||
|
public class DoorwayScriptingParent : DunGenPlusScriptingParent<Doorway> {
|
||||||
|
|
||||||
|
[Header("Scripting Debug")]
|
||||||
|
public Doorway connectedDoorwayDebug;
|
||||||
|
|
||||||
|
public override void Awake(){
|
||||||
|
base.Awake();
|
||||||
|
|
||||||
|
if (targetReference == null) return;
|
||||||
|
|
||||||
|
// steal the scene objects from the doorway and clear them
|
||||||
|
// before the doorway messes with them before us
|
||||||
|
// psycho energy
|
||||||
|
AddNamedReference("connectors", targetReference.ConnectorSceneObjects);
|
||||||
|
targetReference.ConnectorSceneObjects = new List<GameObject>();
|
||||||
|
|
||||||
|
AddNamedReference("blockers", targetReference.BlockerSceneObjects);
|
||||||
|
targetReference.BlockerSceneObjects = new List<GameObject>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Call(){
|
||||||
|
if (targetReference == null) return;
|
||||||
|
|
||||||
|
// start up like in original
|
||||||
|
var isConnected = targetReference.connectedDoorway != null;
|
||||||
|
SetNamedGameObjectState("connectors", isConnected);
|
||||||
|
SetNamedGameObjectState("blockers", !isConnected);
|
||||||
|
|
||||||
|
base.Call();
|
||||||
|
}
|
||||||
|
|
||||||
|
Doorway GetDoorway(string name){
|
||||||
|
switch(name) {
|
||||||
|
case "self":
|
||||||
|
return targetReference;
|
||||||
|
case "other":
|
||||||
|
return InDebugMode ? connectedDoorwayDebug : targetReference.ConnectedDoorway;
|
||||||
|
default:
|
||||||
|
Utils.Utility.PrintLog($"{name} is not valid doorway expression. Please use 'self' or 'other'", BepInEx.Logging.LogLevel.Error);
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override EvaluationContext CreateContext() {
|
||||||
|
var context = new EvaluationContext(GetFields);
|
||||||
|
context.RegisterFunction("doorwaySpawnedGameObject", new FunctionRoutine(2, doorwaySpawnedGameObjectFunction));
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpressionToken doorwaySpawnedGameObjectFunction(EvaluationContext context, ExpressionToken[] parameters) {
|
||||||
|
var targetName = parameters[0].Value;
|
||||||
|
var target = GetDoorway(targetName);
|
||||||
|
if (target != null) {
|
||||||
|
var name = parameters[1].Value;
|
||||||
|
foreach(Transform child in target.transform) {
|
||||||
|
if (child.gameObject.activeSelf && child.name.Contains(name)) return ExpressionToken.True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ExpressionToken.False;
|
||||||
|
}
|
||||||
|
|
||||||
|
(object, ValueTypeHint) GetFields(string field) {
|
||||||
|
var split = field.Split('.');
|
||||||
|
|
||||||
|
if (split.Length <= 1) {
|
||||||
|
Utils.Utility.PrintLog($"{field} is not a valid field", BepInEx.Logging.LogLevel.Error);
|
||||||
|
return (0, ValueTypeHint.Auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetName = split[0];
|
||||||
|
var target = GetDoorway(targetName);
|
||||||
|
var getter = split[1];
|
||||||
|
|
||||||
|
switch(getter) {
|
||||||
|
case "priority":
|
||||||
|
if (target != null){
|
||||||
|
return (target.DoorPrefabPriority, ValueTypeHint.Auto);
|
||||||
|
}
|
||||||
|
return (0, ValueTypeHint.Auto);
|
||||||
|
case "exists":
|
||||||
|
return (target != null, ValueTypeHint.Auto);
|
||||||
|
default:
|
||||||
|
Utils.Utility.PrintLog($"{getter} is not a valid getter", BepInEx.Logging.LogLevel.Error);
|
||||||
|
return (0, ValueTypeHint.Auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
using Soukoku.ExpressionParser;
|
||||||
|
using DunGen;
|
||||||
|
|
||||||
|
namespace DunGenPlus.Components.Scripting {
|
||||||
|
|
||||||
|
public enum ScriptActionType {
|
||||||
|
SwitchToConnector,
|
||||||
|
SwitchToBlocker,
|
||||||
|
SetNamedReferenceState
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
public struct ScriptAction {
|
||||||
|
public ScriptActionType type;
|
||||||
|
public string namedReference;
|
||||||
|
public bool boolValue;
|
||||||
|
|
||||||
|
public void CallAction(IDunGenScriptingParent parent){
|
||||||
|
switch(type){
|
||||||
|
case ScriptActionType.SwitchToConnector:
|
||||||
|
parent.SetNamedGameObjectState("connectors", true);
|
||||||
|
parent.SetNamedGameObjectState("blockers", false);
|
||||||
|
break;
|
||||||
|
case ScriptActionType.SwitchToBlocker:
|
||||||
|
parent.SetNamedGameObjectState("connectors", false);
|
||||||
|
parent.SetNamedGameObjectState("blockers", true);
|
||||||
|
break;
|
||||||
|
case ScriptActionType.SetNamedReferenceState:
|
||||||
|
parent.SetNamedGameObjectState(namedReference, boolValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DunGenPlusScript : MonoBehaviour {
|
||||||
|
|
||||||
|
public static bool InDebugMode = false;
|
||||||
|
|
||||||
|
public string expression;
|
||||||
|
public List<ScriptAction> actions;
|
||||||
|
|
||||||
|
public bool EvaluateExpression(IDunGenScriptingParent parent){
|
||||||
|
var context = parent.CreateContext();
|
||||||
|
var evaluator = new Evaluator(context);
|
||||||
|
try {
|
||||||
|
InDebugMode = false;
|
||||||
|
var results = evaluator.Evaluate(expression, true);
|
||||||
|
return results.ToDouble(context) > 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Plugin.logger.LogError($"Expression [{expression}] could not be parsed. Returning false");
|
||||||
|
Plugin.logger.LogError(e.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Verify")]
|
||||||
|
public void VerifyExpression(){
|
||||||
|
var context = GetComponent<IDunGenScriptingParent>().CreateContext();
|
||||||
|
var evaluator = new Evaluator(context);
|
||||||
|
try {
|
||||||
|
InDebugMode = true;
|
||||||
|
var results = evaluator.Evaluate(expression, false);
|
||||||
|
Debug.Log($"Expression parsed successfully: {results.ToString()} ({evaluator.ConvertTokenToFalseTrue(results).ToString()})");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Debug.LogError($"Expression [{expression}] could not be parsed");
|
||||||
|
Debug.LogError(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Call(IDunGenScriptingParent parent){
|
||||||
|
if (EvaluateExpression(parent)){
|
||||||
|
foreach(var action in actions) action.CallAction(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
using DunGen;
|
||||||
|
using DunGenPlus.Managers;
|
||||||
|
using Soukoku.ExpressionParser;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace DunGenPlus.Components.Scripting {
|
||||||
|
|
||||||
|
public enum OverrideGameObjectState {
|
||||||
|
None,
|
||||||
|
Active,
|
||||||
|
Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
public class NamedGameObjectReference {
|
||||||
|
public string name;
|
||||||
|
public List<GameObject> gameObjects;
|
||||||
|
public OverrideGameObjectState overrideState;
|
||||||
|
|
||||||
|
public NamedGameObjectReference(string name, List<GameObject> gameObjects){
|
||||||
|
this.name = name;
|
||||||
|
this.gameObjects = gameObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetState(bool state){
|
||||||
|
foreach(var g in gameObjects){
|
||||||
|
g?.SetActive(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FixOverrideState(){
|
||||||
|
if (overrideState == OverrideGameObjectState.None) return;
|
||||||
|
SetState(overrideState == OverrideGameObjectState.Active);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DestroyInactiveGameObjects(){
|
||||||
|
foreach(var g in gameObjects){
|
||||||
|
if (g && !g.activeSelf) UnityEngine.Object.DestroyImmediate(g, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DunGenScriptingHook {
|
||||||
|
SetLevelObjectVariables,
|
||||||
|
OnMainEntranceTeleportSpawned
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface IDunGenScriptingParent {
|
||||||
|
|
||||||
|
DunGenScriptingHook GetScriptingHook { get; }
|
||||||
|
|
||||||
|
void Call();
|
||||||
|
|
||||||
|
List<NamedGameObjectReference> GetNamedReferences { get; }
|
||||||
|
|
||||||
|
void AddNamedReference(string name, List<GameObject> gameObjects);
|
||||||
|
|
||||||
|
void SetNamedGameObjectState(string name, bool state);
|
||||||
|
void SetNamedGameObjectOverrideState(string name, OverrideGameObjectState state);
|
||||||
|
|
||||||
|
EvaluationContext CreateContext();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class DunGenPlusScriptingParent<T> : MonoBehaviour, IDunGenScriptingParent, IDungeonCompleteReceiver where T: Component {
|
||||||
|
|
||||||
|
public static bool InDebugMode => DunGenPlusScript.InDebugMode;
|
||||||
|
|
||||||
|
[Header("REQUIRED")]
|
||||||
|
[Tooltip("The target reference.")]
|
||||||
|
public T targetReference;
|
||||||
|
public DunGenScriptingHook callHook = DunGenScriptingHook.OnMainEntranceTeleportSpawned;
|
||||||
|
|
||||||
|
[Header("Named References")]
|
||||||
|
[Tooltip("Provide a variable name for a list of gameObjects. Used in DunGenScripting.")]
|
||||||
|
public List<NamedGameObjectReference> namedReferences = new List<NamedGameObjectReference>();
|
||||||
|
public Dictionary<string, NamedGameObjectReference> namedDictionary = new Dictionary<string, NamedGameObjectReference>();
|
||||||
|
|
||||||
|
public DunGenScriptingHook GetScriptingHook => callHook;
|
||||||
|
public List<NamedGameObjectReference> GetNamedReferences => namedReferences;
|
||||||
|
|
||||||
|
public void OnDungeonComplete(Dungeon dungeon) {
|
||||||
|
//SetBlockers(true);
|
||||||
|
//Debug.Log("ONDUNGEONCOMPLETE");
|
||||||
|
DoorwayManager.AddDunGenScriptHook(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Awake(){
|
||||||
|
foreach(var r in namedReferences){
|
||||||
|
namedDictionary.Add(r.name, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Call() {
|
||||||
|
// call scripts
|
||||||
|
var scripts = GetComponentsInChildren<DunGenPlusScript>();
|
||||||
|
foreach(var c in scripts) c.Call(this);
|
||||||
|
|
||||||
|
// apply any overrides
|
||||||
|
foreach(var n in namedReferences) n.FixOverrideState();
|
||||||
|
|
||||||
|
// clean up like in original
|
||||||
|
foreach(var n in namedReferences) DestroyInactiveGameObjects(n.gameObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddNamedReference(string name, List<GameObject> gameObjects) {
|
||||||
|
var item = new NamedGameObjectReference(name, gameObjects);
|
||||||
|
namedReferences.Add(item);
|
||||||
|
namedDictionary.Add(name, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNamedGameObjectState(string name, bool state){
|
||||||
|
if (namedDictionary.TryGetValue(name, out var obj)){
|
||||||
|
obj.SetState(state);
|
||||||
|
} else {
|
||||||
|
Plugin.logger.LogError($"Named reference: {name} does not exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNamedGameObjectOverrideState(string name, OverrideGameObjectState state){
|
||||||
|
if (namedDictionary.TryGetValue(name, out var obj)){
|
||||||
|
obj.overrideState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DestroyInactiveGameObjects(IEnumerable<GameObject> gameObjects){
|
||||||
|
foreach(var g in gameObjects) {
|
||||||
|
if (g && !g.activeSelf) {
|
||||||
|
UnityEngine.Object.DestroyImmediate(g, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool CheckIfNotNull(object target, string name){
|
||||||
|
if (target == null) {
|
||||||
|
Utils.Utility.PrintLog($"{name} was null", BepInEx.Logging.LogLevel.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract EvaluationContext CreateContext();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ namespace DunGenPlus.DevTools.Panels {
|
|||||||
private GameObject forcedTilesParentGameobject;
|
private GameObject forcedTilesParentGameobject;
|
||||||
private GameObject branchLoopBoostParentGameobject;
|
private GameObject branchLoopBoostParentGameobject;
|
||||||
private GameObject maxShadowsParentGameobject;
|
private GameObject maxShadowsParentGameobject;
|
||||||
|
public bool eventCallbackValue = true;
|
||||||
|
|
||||||
public override void AwakeCall() {
|
public override void AwakeCall() {
|
||||||
Instance = this;
|
Instance = this;
|
||||||
@ -70,6 +71,7 @@ namespace DunGenPlus.DevTools.Panels {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal const string ActivateDunGenPlusTooltip = "If disabled, the dungeon generation will ignore this DunGenPlusExtender asset and simply create a vanilla dungeon instead when generating.";
|
internal const string ActivateDunGenPlusTooltip = "If disabled, the dungeon generation will ignore this DunGenPlusExtender asset and simply create a vanilla dungeon instead when generating.";
|
||||||
|
internal const string EventCallbackScenarioTooltip = "Sets the EventCallbackScenario.IsDevDebug value";
|
||||||
|
|
||||||
public void SetupPanel() {
|
public void SetupPanel() {
|
||||||
selectedExtenderer = API.GetDunGenExtender(selectedDungeonFlow);
|
selectedExtenderer = API.GetDunGenExtender(selectedDungeonFlow);
|
||||||
@ -77,6 +79,7 @@ namespace DunGenPlus.DevTools.Panels {
|
|||||||
var parentTransform = selectedListGameObject.transform;
|
var parentTransform = selectedListGameObject.transform;
|
||||||
var properties = selectedExtenderer.Properties;
|
var properties = selectedExtenderer.Properties;
|
||||||
manager.CreateBoolInputField(parentTransform, ("Activate DunGenPlus", ActivateDunGenPlusTooltip), selectedExtenderer.Active, SetActivateDunGenPlus);
|
manager.CreateBoolInputField(parentTransform, ("Activate DunGenPlus", ActivateDunGenPlusTooltip), selectedExtenderer.Active, SetActivateDunGenPlus);
|
||||||
|
manager.CreateBoolInputField(parentTransform, ("EventCallbackScenario state", EventCallbackScenarioTooltip), eventCallbackValue, SetDebugCallbackState);
|
||||||
manager.CreateSpaceUIField(parentTransform);
|
manager.CreateSpaceUIField(parentTransform);
|
||||||
|
|
||||||
var mainPathTransform = manager.CreateVerticalLayoutUIField(parentTransform);
|
var mainPathTransform = manager.CreateVerticalLayoutUIField(parentTransform);
|
||||||
@ -168,6 +171,10 @@ namespace DunGenPlus.DevTools.Panels {
|
|||||||
selectedExtenderer.Active = state;
|
selectedExtenderer.Active = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetDebugCallbackState(bool state){
|
||||||
|
eventCallbackValue = state;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetMainPathCount(int value) {
|
public void SetMainPathCount(int value) {
|
||||||
selectedExtenderer.Properties.MainPathProperties.MainPathCount = value;
|
selectedExtenderer.Properties.MainPathProperties.MainPathCount = value;
|
||||||
mainPathParentGameobject.SetActive(value > 1);
|
mainPathParentGameobject.SetActive(value > 1);
|
||||||
|
@ -56,6 +56,9 @@
|
|||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\..\..\Libraries\MonoMod.Utils.dll</HintPath>
|
<HintPath>..\..\..\Libraries\MonoMod.Utils.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
@ -157,7 +160,24 @@
|
|||||||
<Compile Include="Components\MainRoomDoorwayGroups.cs" />
|
<Compile Include="Components\MainRoomDoorwayGroups.cs" />
|
||||||
<Compile Include="Components\Props\SpawnSyncedObjectCycle.cs" />
|
<Compile Include="Components\Props\SpawnSyncedObjectCycle.cs" />
|
||||||
<Compile Include="Components\Scrap\RandomGuaranteedScrapSpawn.cs" />
|
<Compile Include="Components\Scrap\RandomGuaranteedScrapSpawn.cs" />
|
||||||
|
<Compile Include="Components\Scripting\DoorwayScriptingParent.cs" />
|
||||||
|
<Compile Include="Components\Scripting\DunGenPlusScript.cs" />
|
||||||
|
<Compile Include="Components\Scripting\DunGenPlusScriptingParent.cs" />
|
||||||
<Compile Include="DevTools\Panels\AssetsPanel.cs" />
|
<Compile Include="DevTools\Panels\AssetsPanel.cs" />
|
||||||
|
<Compile Include="ExpressionParser\EvaluationContext.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Evaluator.cs" />
|
||||||
|
<Compile Include="ExpressionParser\ExpressionToken.cs" />
|
||||||
|
<Compile Include="ExpressionParser\ExpressionTokenType.cs" />
|
||||||
|
<Compile Include="ExpressionParser\FunctionRoutine.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Parsing\IExpressionTokenizer.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Parsing\InfixTokenizer.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Parsing\InfixToPostfixTokenizer.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Parsing\KnownOperators.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Parsing\ListReader.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Parsing\OperatorType.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Parsing\RawToken.cs" />
|
||||||
|
<Compile Include="ExpressionParser\Parsing\RawTokenizer.cs" />
|
||||||
|
<Compile Include="ExpressionParser\ValueTypeHint.cs" />
|
||||||
<Compile Include="Generation\DunGenPlusGenerationPaths.cs" />
|
<Compile Include="Generation\DunGenPlusGenerationPaths.cs" />
|
||||||
<Compile Include="Generation\DunGenPlusGeneratorDebug.cs" />
|
<Compile Include="Generation\DunGenPlusGeneratorDebug.cs" />
|
||||||
<Compile Include="Generation\DunGenPlusGeneratorGlobalProps.cs" />
|
<Compile Include="Generation\DunGenPlusGeneratorGlobalProps.cs" />
|
||||||
|
Binary file not shown.
Binary file not shown.
136
DunGenPlus/DunGenPlus/ExpressionParser/EvaluationContext.cs
Normal file
136
DunGenPlus/DunGenPlus/ExpressionParser/EvaluationContext.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Context for storing and returning values during an expression evaluation.
|
||||||
|
/// </summary>
|
||||||
|
public class EvaluationContext
|
||||||
|
{
|
||||||
|
static Dictionary<string, FunctionRoutine> BuiltInFunctions = new Dictionary<string, FunctionRoutine>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "pow", new FunctionRoutine(2, (ctx, args)=>
|
||||||
|
new ExpressionToken( Math.Pow(args[0].ToDouble(ctx), args[1].ToDouble(ctx)).ToString(ctx.FormatCulture))) },
|
||||||
|
{ "sin", new FunctionRoutine(1, (ctx, args)=>
|
||||||
|
new ExpressionToken( Math.Sin(args[0].ToDouble(ctx)).ToString(ctx.FormatCulture)))},
|
||||||
|
{ "cos", new FunctionRoutine(1, (ctx, args)=>
|
||||||
|
new ExpressionToken( Math.Cos(args[0].ToDouble(ctx)).ToString(ctx.FormatCulture)))},
|
||||||
|
{ "tan", new FunctionRoutine(1, (ctx, args)=>
|
||||||
|
new ExpressionToken( Math.Tan(args[0].ToDouble(ctx)).ToString(ctx.FormatCulture)))}
|
||||||
|
};
|
||||||
|
|
||||||
|
static readonly Dictionary<string, FunctionRoutine> __staticFuncs = new Dictionary<string, FunctionRoutine>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
readonly Dictionary<string, FunctionRoutine> _instanceFuncs = new Dictionary<string, FunctionRoutine>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
Func<string, (object, ValueTypeHint)> _fieldLookup;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="EvaluationContext"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public EvaluationContext() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="EvaluationContext"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldLookupRoutine">The field value lookup routine.</param>
|
||||||
|
public EvaluationContext(Func<string, (object Value, ValueTypeHint TypeHint)> fieldLookupRoutine)
|
||||||
|
{
|
||||||
|
_fieldLookup = fieldLookupRoutine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the field value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">The field.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public (object Value, ValueTypeHint TypeHint) ResolveFieldValue(string field)
|
||||||
|
{
|
||||||
|
if (_fieldLookup != null) { return _fieldLookup(field); }
|
||||||
|
return OnResolveFieldValue(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
private CultureInfo _formatCulture = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/sets the culture used to parse/format expressions.
|
||||||
|
/// Defaults to en-US for certain reasons.
|
||||||
|
/// </summary>
|
||||||
|
public CultureInfo FormatCulture
|
||||||
|
{
|
||||||
|
get { return _formatCulture ?? _usCulture; }
|
||||||
|
set { _formatCulture = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the field value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">The field.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual (object Value, ValueTypeHint TypeHint) OnResolveFieldValue(string field)
|
||||||
|
{
|
||||||
|
return (string.Empty, ValueTypeHint.Auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a custom function globally.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">Name of the function.</param>
|
||||||
|
/// <param name="info">The information.</param>
|
||||||
|
public static void RegisterGlobalFunction(string functionName, FunctionRoutine info)
|
||||||
|
{
|
||||||
|
__staticFuncs[functionName] = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a custom function with this context instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">Name of the function.</param>
|
||||||
|
/// <param name="info">The information.</param>
|
||||||
|
public void RegisterFunction(string functionName, FunctionRoutine info)
|
||||||
|
{
|
||||||
|
_instanceFuncs[functionName] = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the function registered with this context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">Name of the function.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.NotSupportedException"></exception>
|
||||||
|
public FunctionRoutine GetFunction(string functionName)
|
||||||
|
{
|
||||||
|
if (_instanceFuncs.ContainsKey(functionName))
|
||||||
|
{
|
||||||
|
return _instanceFuncs[functionName];
|
||||||
|
}
|
||||||
|
if (__staticFuncs.ContainsKey(functionName))
|
||||||
|
{
|
||||||
|
return __staticFuncs[functionName];
|
||||||
|
}
|
||||||
|
if (BuiltInFunctions.ContainsKey(functionName))
|
||||||
|
{
|
||||||
|
return BuiltInFunctions[functionName];
|
||||||
|
}
|
||||||
|
return OnGetFunction(functionName) ??
|
||||||
|
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Function \"{0}\" is not supported.", functionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the function registered with this context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">Name of the function.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.NotSupportedException"></exception>
|
||||||
|
protected virtual FunctionRoutine OnGetFunction(string functionName)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
438
DunGenPlus/DunGenPlus/ExpressionParser/Evaluator.cs
Normal file
438
DunGenPlus/DunGenPlus/ExpressionParser/Evaluator.cs
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
using Soukoku.ExpressionParser.Parsing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An expression evaluator.
|
||||||
|
/// </summary>
|
||||||
|
public class Evaluator
|
||||||
|
{
|
||||||
|
EvaluationContext _context;
|
||||||
|
Stack<ExpressionToken> _stack;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Evaluator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">context</exception>
|
||||||
|
public Evaluator(EvaluationContext context)
|
||||||
|
{
|
||||||
|
_context = context ?? throw new ArgumentNullException("context");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates the specified input expression.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The input expression (infix).</param>
|
||||||
|
/// <param name="coerseToBoolean">if set to <c>true</c> then the result will be coersed to boolean true/false if possible.
|
||||||
|
/// Anything not "false", "0", or "" is considered true.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotSupportedException">Unbalanced expression.</exception>
|
||||||
|
/// <exception cref="System.NotSupportedException"></exception>
|
||||||
|
public ExpressionToken Evaluate(string input, bool coerseToBoolean = false)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
return coerseToBoolean ? ExpressionToken.False : new ExpressionToken(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokens = new InfixToPostfixTokenizer().Tokenize(input);
|
||||||
|
// resolve field value and type hints here
|
||||||
|
foreach (var token in tokens.Where(tk => tk.TokenType == ExpressionTokenType.Field))
|
||||||
|
{
|
||||||
|
token.FieldValue = _context.ResolveFieldValue(token.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var reader = new ListReader<ExpressionToken>(tokens);
|
||||||
|
|
||||||
|
// from https://en.wikipedia.org/wiki/Reverse_Polish_notation
|
||||||
|
_stack = new Stack<ExpressionToken>();
|
||||||
|
while (!reader.IsEnd)
|
||||||
|
{
|
||||||
|
var tk = reader.Read();
|
||||||
|
switch (tk.TokenType)
|
||||||
|
{
|
||||||
|
case ExpressionTokenType.Value:
|
||||||
|
case ExpressionTokenType.DoubleQuoted:
|
||||||
|
case ExpressionTokenType.SingleQuoted:
|
||||||
|
case ExpressionTokenType.Field:
|
||||||
|
_stack.Push(tk);
|
||||||
|
break;
|
||||||
|
case ExpressionTokenType.Operator:
|
||||||
|
HandleOperator(tk.OperatorType);
|
||||||
|
break;
|
||||||
|
case ExpressionTokenType.Function:
|
||||||
|
HandleFunction(tk.Value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_stack.Count == 1)
|
||||||
|
{
|
||||||
|
var res = _stack.Pop();
|
||||||
|
if (coerseToBoolean)
|
||||||
|
{
|
||||||
|
return ConvertTokenToFalseTrue(res);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
|
||||||
|
//if (res.IsNumeric())
|
||||||
|
//{
|
||||||
|
// return res;
|
||||||
|
//}
|
||||||
|
//else if (IsTrue(res.Value))
|
||||||
|
//{
|
||||||
|
// return ExpressionToken.True;
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
throw new NotSupportedException("Unbalanced expression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExpressionToken ConvertTokenToFalseTrue(ExpressionToken token){
|
||||||
|
// changed form Value to ToString() so fields by themselves evalute properly
|
||||||
|
if (IsFalse(token.ToString()))
|
||||||
|
{
|
||||||
|
return ExpressionToken.False;
|
||||||
|
}
|
||||||
|
return ExpressionToken.True;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleFunction(string functionName)
|
||||||
|
{
|
||||||
|
var fun = _context.GetFunction(functionName);
|
||||||
|
var args = new Stack<ExpressionToken>(fun.ArgumentCount);
|
||||||
|
|
||||||
|
while (args.Count < fun.ArgumentCount)
|
||||||
|
{
|
||||||
|
args.Push(_stack.Pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
_stack.Push(fun.Evaluate(_context, args.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region operator handling
|
||||||
|
|
||||||
|
static bool IsDate(string lhs, string rhs, out DateTime lhsDate, out DateTime rhsDate)
|
||||||
|
{
|
||||||
|
lhsDate = default(DateTime);
|
||||||
|
rhsDate = default(DateTime);
|
||||||
|
|
||||||
|
if (DateTime.TryParse(lhs, out lhsDate))
|
||||||
|
{
|
||||||
|
DateTime.TryParse(rhs, out rhsDate);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (DateTime.TryParse(rhs, out rhsDate))
|
||||||
|
{
|
||||||
|
DateTime.TryParse(lhs, out lhsDate);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool IsNumber(string lhs, string rhs, out decimal lhsNumber, out decimal rhsNumber)
|
||||||
|
{
|
||||||
|
lhsNumber = 0;
|
||||||
|
rhsNumber = 0;
|
||||||
|
|
||||||
|
var islNum = decimal.TryParse(lhs, ExpressionToken.NumberParseStyle, _context.FormatCulture, out lhsNumber);
|
||||||
|
var isrNum = decimal.TryParse(rhs, ExpressionToken.NumberParseStyle, _context.FormatCulture, out rhsNumber);
|
||||||
|
|
||||||
|
return islNum && isrNum;
|
||||||
|
}
|
||||||
|
static bool IsBoolean(string lhs, string rhs, out bool lhsBool, out bool rhsBool)
|
||||||
|
{
|
||||||
|
bool lIsBool = false;
|
||||||
|
bool rIsBool = false;
|
||||||
|
lhsBool = false;
|
||||||
|
rhsBool = false;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(lhs))
|
||||||
|
{
|
||||||
|
if (string.Equals(lhs, "true", StringComparison.OrdinalIgnoreCase) || lhs == "1")
|
||||||
|
{
|
||||||
|
lhsBool = true;
|
||||||
|
lIsBool = true;
|
||||||
|
}
|
||||||
|
else if (string.Equals(lhs, "false", StringComparison.OrdinalIgnoreCase) || lhs == "0")
|
||||||
|
{
|
||||||
|
lIsBool = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lIsBool && !string.IsNullOrEmpty(rhs))
|
||||||
|
{
|
||||||
|
if (string.Equals(rhs, "true", StringComparison.OrdinalIgnoreCase) || rhs == "1")
|
||||||
|
{
|
||||||
|
rhsBool = true;
|
||||||
|
rIsBool = true;
|
||||||
|
}
|
||||||
|
else if (string.Equals(rhs, "false", StringComparison.OrdinalIgnoreCase) || rhs == "0")
|
||||||
|
{
|
||||||
|
rIsBool = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lIsBool && rIsBool;
|
||||||
|
|
||||||
|
//lhsBool = false;
|
||||||
|
//rhsBool = false;
|
||||||
|
|
||||||
|
//if (string.Equals(lhs, "true", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(rhs))
|
||||||
|
//{
|
||||||
|
// lhsBool = true;
|
||||||
|
// rhsBool = IsTrue(rhs);
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
|
//else if (string.Equals(lhs, "false", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(rhs))
|
||||||
|
//{
|
||||||
|
// rhsBool = IsTrue(rhs);
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
|
//else if (string.Equals(rhs, "true", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(lhs))
|
||||||
|
//{
|
||||||
|
// rhsBool = true;
|
||||||
|
// lhsBool = IsTrue(lhs);
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
|
//else if (string.Equals(rhs, "false", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(lhs))
|
||||||
|
//{
|
||||||
|
// lhsBool = IsTrue(lhs);
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
|
//return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleOperator(OperatorType op)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case OperatorType.Addition:
|
||||||
|
BinaryNumberOperation((a, b) => a + b);
|
||||||
|
break;
|
||||||
|
case OperatorType.Subtraction:
|
||||||
|
BinaryNumberOperation((a, b) => a - b);
|
||||||
|
break;
|
||||||
|
case OperatorType.Multiplication:
|
||||||
|
BinaryNumberOperation((a, b) => a * b);
|
||||||
|
break;
|
||||||
|
case OperatorType.Division:
|
||||||
|
BinaryNumberOperation((a, b) => a / b);
|
||||||
|
break;
|
||||||
|
case OperatorType.Modulus:
|
||||||
|
BinaryNumberOperation((a, b) => a % b);
|
||||||
|
break;
|
||||||
|
// these logical comparision can be date/num/string!
|
||||||
|
case OperatorType.LessThan:
|
||||||
|
var rhsToken = _stack.Pop();
|
||||||
|
var lhsToken = _stack.Pop();
|
||||||
|
var rhs = rhsToken.ToString();
|
||||||
|
var lhs = lhsToken.ToString();
|
||||||
|
|
||||||
|
if (IsNumber(lhs, rhs, out decimal lhsNum, out decimal rhsNum))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsNum < rhsNum ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else if (IsDate(lhs, rhs, out DateTime lhsDate, out DateTime rhsDate))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsDate < rhsDate ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stack.Push(string.Compare(lhs, rhs, StringComparison.OrdinalIgnoreCase) < 0 ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperatorType.LessThanOrEqual:
|
||||||
|
rhsToken = _stack.Pop();
|
||||||
|
lhsToken = _stack.Pop();
|
||||||
|
rhs = rhsToken.ToString();
|
||||||
|
lhs = lhsToken.ToString();
|
||||||
|
|
||||||
|
if (IsNumber(lhs, rhs, out lhsNum, out rhsNum))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsNum <= rhsNum ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else if (IsDate(lhs, rhs, out DateTime lhsDate, out DateTime rhsDate))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsDate <= rhsDate ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stack.Push(string.Compare(lhs, rhs, StringComparison.OrdinalIgnoreCase) <= 0 ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperatorType.GreaterThan:
|
||||||
|
rhsToken = _stack.Pop();
|
||||||
|
lhsToken = _stack.Pop();
|
||||||
|
rhs = rhsToken.ToString();
|
||||||
|
lhs = lhsToken.ToString();
|
||||||
|
|
||||||
|
if (IsNumber(lhs, rhs, out lhsNum, out rhsNum))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsNum > rhsNum ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else if (IsDate(lhs, rhs, out DateTime lhsDate, out DateTime rhsDate))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsDate > rhsDate ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stack.Push(string.Compare(lhs, rhs, StringComparison.OrdinalIgnoreCase) > 0 ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperatorType.GreaterThanOrEqual:
|
||||||
|
rhsToken = _stack.Pop();
|
||||||
|
lhsToken = _stack.Pop();
|
||||||
|
rhs = rhsToken.ToString();
|
||||||
|
lhs = lhsToken.ToString();
|
||||||
|
|
||||||
|
if (IsNumber(lhs, rhs, out lhsNum, out rhsNum))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsNum >= rhsNum ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else if (IsDate(lhs, rhs, out DateTime lhsDate, out DateTime rhsDate))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsDate >= rhsDate ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stack.Push(string.Compare(lhs, rhs, StringComparison.OrdinalIgnoreCase) >= 0 ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperatorType.Equal:
|
||||||
|
rhsToken = _stack.Pop();
|
||||||
|
lhsToken = _stack.Pop();
|
||||||
|
rhs = rhsToken.ToString();
|
||||||
|
lhs = lhsToken.ToString();
|
||||||
|
|
||||||
|
if (IsBoolean(lhs, rhs, out bool lhsBool, out bool rhsBool))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsBool == rhsBool ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else if ((AllowAutoFormat(lhsToken) || AllowAutoFormat(rhsToken)) &&
|
||||||
|
IsNumber(lhs, rhs, out lhsNum, out rhsNum))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsNum == rhsNum ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else if (IsDate(lhs, rhs, out DateTime lhsDate, out DateTime rhsDate))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsDate == rhsDate ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stack.Push(string.Compare(lhs, rhs, StringComparison.OrdinalIgnoreCase) == 0 ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperatorType.NotEqual:
|
||||||
|
rhsToken = _stack.Pop();
|
||||||
|
lhsToken = _stack.Pop();
|
||||||
|
rhs = rhsToken.ToString();
|
||||||
|
lhs = lhsToken.ToString();
|
||||||
|
|
||||||
|
if (IsBoolean(lhs, rhs, out lhsBool, out rhsBool))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsBool != rhsBool ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else if ((AllowAutoFormat(lhsToken) || AllowAutoFormat(rhsToken)) &&
|
||||||
|
IsNumber(lhs, rhs, out lhsNum, out rhsNum))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsNum != rhsNum ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else if (IsDate(lhs, rhs, out DateTime lhsDate, out DateTime rhsDate))
|
||||||
|
{
|
||||||
|
_stack.Push(lhsDate != rhsDate ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stack.Push(string.Compare(lhs, rhs, StringComparison.OrdinalIgnoreCase) != 0 ? ExpressionToken.True : ExpressionToken.False);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperatorType.BitwiseAnd:
|
||||||
|
BinaryNumberOperation((a, b) => (int)a & (int)b);
|
||||||
|
break;
|
||||||
|
case OperatorType.BitwiseOr:
|
||||||
|
BinaryNumberOperation((a, b) => (int)a | (int)b);
|
||||||
|
break;
|
||||||
|
case OperatorType.LogicalAnd:
|
||||||
|
BinaryLogicOperation((a, b) => IsTrue(a) && IsTrue(b));
|
||||||
|
break;
|
||||||
|
case OperatorType.LogicalOr:
|
||||||
|
BinaryLogicOperation((a, b) => IsTrue(a) || IsTrue(b));
|
||||||
|
break;
|
||||||
|
case OperatorType.UnaryMinus:
|
||||||
|
UnaryNumberOperation(a => -1 * a);
|
||||||
|
break;
|
||||||
|
case OperatorType.UnaryPlus:
|
||||||
|
// no action
|
||||||
|
break;
|
||||||
|
case OperatorType.LogicalNegation:
|
||||||
|
UnaryLogicOperation(a => !IsTrue(a));
|
||||||
|
break;
|
||||||
|
case OperatorType.PreIncrement:
|
||||||
|
UnaryNumberOperation(a => a + 1);
|
||||||
|
break;
|
||||||
|
case OperatorType.PreDecrement:
|
||||||
|
UnaryNumberOperation(a => a - 1);
|
||||||
|
break;
|
||||||
|
// TODO: handle assignments & post increments
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "The {0} operation is not currently supported.", op));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool AllowAutoFormat(ExpressionToken token)
|
||||||
|
{
|
||||||
|
return token.TokenType != ExpressionTokenType.Field || token.FieldValue.TypeHint != ValueTypeHint.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsTrue(string value)
|
||||||
|
{
|
||||||
|
return string.Equals("true", value, StringComparison.OrdinalIgnoreCase) || value == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsFalse(string value)
|
||||||
|
{
|
||||||
|
return string.Equals("false", value, StringComparison.OrdinalIgnoreCase) || value == "0" || string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnaryNumberOperation(Func<decimal, decimal> operation)
|
||||||
|
{
|
||||||
|
var op1 = _stack.Pop().ToDecimal(_context);
|
||||||
|
var res = operation(op1);
|
||||||
|
|
||||||
|
_stack.Push(new ExpressionToken(res.ToString(_context.FormatCulture)));
|
||||||
|
}
|
||||||
|
void UnaryLogicOperation(Func<string, bool> operation)
|
||||||
|
{
|
||||||
|
var op1 = _stack.Pop();
|
||||||
|
var res = operation(op1.ToString()) ? "1" : "0";
|
||||||
|
|
||||||
|
_stack.Push(new ExpressionToken(res));
|
||||||
|
}
|
||||||
|
void BinaryLogicOperation(Func<string, string, bool> operation)
|
||||||
|
{
|
||||||
|
var op2 = _stack.Pop();
|
||||||
|
var op1 = _stack.Pop();
|
||||||
|
|
||||||
|
var res = operation(op1.ToString(), op2.ToString()) ? "1" : "0";
|
||||||
|
|
||||||
|
_stack.Push(new ExpressionToken(res));
|
||||||
|
}
|
||||||
|
void BinaryNumberOperation(Func<decimal, decimal, decimal> operation)
|
||||||
|
{
|
||||||
|
var op2 = _stack.Pop().ToDecimal(_context);
|
||||||
|
var op1 = _stack.Pop().ToDecimal(_context);
|
||||||
|
|
||||||
|
var res = operation(op1, op2);
|
||||||
|
|
||||||
|
_stack.Push(new ExpressionToken(res.ToString(_context.FormatCulture)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
221
DunGenPlus/DunGenPlus/ExpressionParser/ExpressionToken.cs
Normal file
221
DunGenPlus/DunGenPlus/ExpressionParser/ExpressionToken.cs
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Globalization;
|
||||||
|
using Soukoku.ExpressionParser.Parsing;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A token split from the initial text input.
|
||||||
|
/// </summary>
|
||||||
|
public class ExpressionToken
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Canonical true value. Actual value is numerical "1".
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ExpressionToken True = new ExpressionToken("1");
|
||||||
|
/// <summary>
|
||||||
|
/// Canonical false value. Actual value is numerical "0".
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ExpressionToken False = new ExpressionToken("0");
|
||||||
|
|
||||||
|
internal static readonly NumberStyles NumberParseStyle = NumberStyles.Integer | NumberStyles.AllowDecimalPoint | NumberStyles.AllowCurrencySymbol | NumberStyles.Number;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ExpressionToken"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public ExpressionToken() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new frozen instance of the <see cref="ExpressionToken"/> class
|
||||||
|
/// with the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value.</param>
|
||||||
|
public ExpressionToken(string value)
|
||||||
|
{
|
||||||
|
_type = ExpressionTokenType.Value;
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RawToken _rawToken; // the raw token that makes this token
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw token that made this list.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public RawToken RawToken { get { return _rawToken; } }
|
||||||
|
|
||||||
|
const string FrozenErrorMsg = "Cannot modify frozen token.";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends the specified token to this expression.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">The token.</param>
|
||||||
|
/// <exception cref="System.InvalidOperationException"></exception>
|
||||||
|
public void Append(RawToken token)
|
||||||
|
{
|
||||||
|
if (IsFrozen) { throw new InvalidOperationException(FrozenErrorMsg); }
|
||||||
|
|
||||||
|
if (_rawToken == null) { _rawToken = token; }
|
||||||
|
else { _rawToken.Append(token); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this instance is frozen from append.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if this instance is frozen; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public bool IsFrozen { get { return _value != null; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Freezes this instance from being appended.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="System.InvalidOperationException"></exception>
|
||||||
|
public void Freeze()
|
||||||
|
{
|
||||||
|
if (IsFrozen) { throw new InvalidOperationException(FrozenErrorMsg); }
|
||||||
|
|
||||||
|
_value = _rawToken?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExpressionTokenType _type;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of the token.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The type of the token.
|
||||||
|
/// </value>
|
||||||
|
public ExpressionTokenType TokenType
|
||||||
|
{
|
||||||
|
get { return _type; }
|
||||||
|
set { if (_value == null) { _type = value; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of the operator. This is only used if the <see cref="TokenType"/>
|
||||||
|
/// is <see cref="ExpressionTokenType.Operator"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The type of the operator.
|
||||||
|
/// </value>
|
||||||
|
public OperatorType OperatorType { get; set; }
|
||||||
|
|
||||||
|
string _value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw token value.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The value.
|
||||||
|
/// </value>
|
||||||
|
public string Value { get { return _value ?? _rawToken?.ToString(); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the resolved field value and type hint if token is a field.
|
||||||
|
/// </summary>
|
||||||
|
public (object Value, ValueTypeHint TypeHint) FieldValue { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="System.String" /> that represents this instance.
|
||||||
|
/// </returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
switch (TokenType)
|
||||||
|
{
|
||||||
|
case ExpressionTokenType.Field:
|
||||||
|
return FieldValue.Value?.ToString() ?? "";
|
||||||
|
default:
|
||||||
|
return Value ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region conversion routines
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// Check if the value is considered numeric.
|
||||||
|
///// </summary>
|
||||||
|
///// <returns></returns>
|
||||||
|
//public bool IsNumeric()
|
||||||
|
//{
|
||||||
|
// if (TokenType == ExpressionTokenType.Field && FieldValue.TypeHint == ValueTypeHint.Text) return false;
|
||||||
|
|
||||||
|
// return decimal.TryParse(Value, NumberParseStyle, CultureInfo.InvariantCulture, out decimal dummy);
|
||||||
|
//}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// Check if the value is considered true.
|
||||||
|
///// </summary>
|
||||||
|
///// <returns></returns>
|
||||||
|
//public bool IsTrue(string value)
|
||||||
|
//{
|
||||||
|
// if (TokenType == ExpressionTokenType.Field && FieldValue.TypeHint == ValueTypeHint.Text) return false;
|
||||||
|
|
||||||
|
// return string.Equals("true", Value, StringComparison.OrdinalIgnoreCase) || value == "1";
|
||||||
|
//}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// Check if the value is considered false.
|
||||||
|
///// </summary>
|
||||||
|
///// <returns></returns>
|
||||||
|
//public bool IsFalse(string value)
|
||||||
|
//{
|
||||||
|
// if (TokenType == ExpressionTokenType.Field && FieldValue.TypeHint == ValueTypeHint.Text) return false;
|
||||||
|
|
||||||
|
// return string.Equals("false", Value, StringComparison.OrdinalIgnoreCase) || value == "0";
|
||||||
|
//}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts to the double value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.NotSupportedException"></exception>
|
||||||
|
/// <exception cref="FormatException"></exception>
|
||||||
|
public double ToDouble(EvaluationContext context)
|
||||||
|
{
|
||||||
|
switch (TokenType)
|
||||||
|
{
|
||||||
|
case ExpressionTokenType.Value:
|
||||||
|
case ExpressionTokenType.SingleQuoted:
|
||||||
|
case ExpressionTokenType.DoubleQuoted:
|
||||||
|
return double.Parse(Value, NumberParseStyle, context.FormatCulture);
|
||||||
|
case ExpressionTokenType.Field:
|
||||||
|
return double.Parse(FieldValue.Value?.ToString(), NumberParseStyle, context.FormatCulture);
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Cannot convert {0}({1}) to a numeric value.", TokenType, Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts to the decimal value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.NotSupportedException"></exception>
|
||||||
|
/// <exception cref="FormatException"></exception>
|
||||||
|
public decimal ToDecimal(EvaluationContext context)
|
||||||
|
{
|
||||||
|
switch (TokenType)
|
||||||
|
{
|
||||||
|
case ExpressionTokenType.Value:
|
||||||
|
case ExpressionTokenType.SingleQuoted:
|
||||||
|
case ExpressionTokenType.DoubleQuoted:
|
||||||
|
return decimal.Parse(Value, NumberParseStyle, context.FormatCulture);
|
||||||
|
case ExpressionTokenType.Field:
|
||||||
|
return decimal.Parse(FieldValue.Value?.ToString(), NumberParseStyle, context.FormatCulture);
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Cannot convert {0}({1}) to a numeric value.", TokenType, Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates the expression token type.
|
||||||
|
/// </summary>
|
||||||
|
public enum ExpressionTokenType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid token type.
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is an operator.
|
||||||
|
/// </summary>
|
||||||
|
Operator,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is an open parenthesis.
|
||||||
|
/// </summary>
|
||||||
|
OpenParenthesis,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is a close parenthesis.
|
||||||
|
/// </summary>
|
||||||
|
CloseParenthesis,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is a function.
|
||||||
|
/// </summary>
|
||||||
|
Function,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is a comma.
|
||||||
|
/// </summary>
|
||||||
|
Comma,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is a field reference.
|
||||||
|
/// </summary>
|
||||||
|
Field,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is from single quoted value.
|
||||||
|
/// </summary>
|
||||||
|
SingleQuoted,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is from double quoted value.
|
||||||
|
/// </summary>
|
||||||
|
DoubleQuoted,
|
||||||
|
/// <summary>
|
||||||
|
/// The token is a yet-to-be-parsed value.
|
||||||
|
/// </summary>
|
||||||
|
Value,
|
||||||
|
}
|
||||||
|
}
|
44
DunGenPlus/DunGenPlus/ExpressionParser/FunctionRoutine.cs
Normal file
44
DunGenPlus/DunGenPlus/ExpressionParser/FunctionRoutine.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a basic function routine.
|
||||||
|
/// </summary>
|
||||||
|
public class FunctionRoutine
|
||||||
|
{
|
||||||
|
Func<EvaluationContext, ExpressionToken[], ExpressionToken> _routine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="FunctionRoutine"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argCount">The argument count.</param>
|
||||||
|
/// <param name="routine">The routine.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">routine</exception>
|
||||||
|
public FunctionRoutine(int argCount, Func<EvaluationContext, ExpressionToken[], ExpressionToken> routine)
|
||||||
|
{
|
||||||
|
if (routine == null) { throw new ArgumentNullException("routine"); }
|
||||||
|
ArgumentCount = argCount;
|
||||||
|
_routine = routine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the expected argument count.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The argument count.
|
||||||
|
/// </value>
|
||||||
|
public int ArgumentCount { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates using the function routine.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context.</param>
|
||||||
|
/// <param name="args">The arguments.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ExpressionToken Evaluate(EvaluationContext context, ExpressionToken[] args) { return _routine(context, args); }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
namespace Soukoku.ExpressionParser.Parsing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for something that can tokenize an expression.
|
||||||
|
/// </summary>
|
||||||
|
public interface IExpressionTokenizer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Splits the specified input expression into a list of <see cref="ExpressionToken" /> values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The input.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.NotSupportedException"></exception>
|
||||||
|
ExpressionToken[] Tokenize(string input);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser.Parsing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A tokenizer that parses an input expression string in infix notation into tokens without white spaces
|
||||||
|
/// in the orders of postfix expressions.
|
||||||
|
/// </summary>
|
||||||
|
public class InfixToPostfixTokenizer : IExpressionTokenizer
|
||||||
|
{
|
||||||
|
const string UnbalancedParenMsg = "Unbalanced parenthesis in expression.";
|
||||||
|
|
||||||
|
List<ExpressionToken> _output;
|
||||||
|
Stack<ExpressionToken> _stack;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits the specified input into a list of <see cref="ExpressionToken" /> values
|
||||||
|
/// in postfix order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The input.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.NotSupportedException"></exception>
|
||||||
|
public ExpressionToken[] Tokenize(string input)
|
||||||
|
{
|
||||||
|
var infixTokens = new InfixTokenizer().Tokenize(input);
|
||||||
|
_output = new List<ExpressionToken>();
|
||||||
|
_stack = new Stack<ExpressionToken>();
|
||||||
|
|
||||||
|
// this is the shunting-yard algorithm
|
||||||
|
// https://en.wikipedia.org/wiki/Shunting-yard_algorithm
|
||||||
|
|
||||||
|
foreach (var inToken in infixTokens)
|
||||||
|
{
|
||||||
|
switch (inToken.TokenType)
|
||||||
|
{
|
||||||
|
case ExpressionTokenType.Value:
|
||||||
|
case ExpressionTokenType.DoubleQuoted:
|
||||||
|
case ExpressionTokenType.SingleQuoted:
|
||||||
|
case ExpressionTokenType.Field:
|
||||||
|
_output.Add(inToken);
|
||||||
|
break;
|
||||||
|
case ExpressionTokenType.Function:
|
||||||
|
_stack.Push(inToken);
|
||||||
|
break;
|
||||||
|
case ExpressionTokenType.Comma:
|
||||||
|
HandleComma();
|
||||||
|
break;
|
||||||
|
case ExpressionTokenType.Operator:
|
||||||
|
HandleOperatorToken(inToken);
|
||||||
|
break;
|
||||||
|
case ExpressionTokenType.OpenParenthesis:
|
||||||
|
_stack.Push(inToken);
|
||||||
|
break;
|
||||||
|
case ExpressionTokenType.CloseParenthesis:
|
||||||
|
HandleCloseParenthesis();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_stack.Count > 0)
|
||||||
|
{
|
||||||
|
var op = _stack.Pop();
|
||||||
|
if (op.TokenType == ExpressionTokenType.OpenParenthesis)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(UnbalancedParenMsg);
|
||||||
|
}
|
||||||
|
_output.Add(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _output.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleComma()
|
||||||
|
{
|
||||||
|
bool closed = false;
|
||||||
|
while (_stack.Count > 1)
|
||||||
|
{
|
||||||
|
var peek = _stack.Peek();
|
||||||
|
if (peek.TokenType == ExpressionTokenType.OpenParenthesis)
|
||||||
|
{
|
||||||
|
closed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_output.Add(_stack.Pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!closed)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(UnbalancedParenMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleOperatorToken(ExpressionToken inToken)
|
||||||
|
{
|
||||||
|
while (_stack.Count > 0)
|
||||||
|
{
|
||||||
|
var op2 = _stack.Peek();
|
||||||
|
if (op2.TokenType == ExpressionTokenType.Operator)
|
||||||
|
{
|
||||||
|
var op1Prec = KnownOperators.GetPrecedence(inToken.OperatorType);
|
||||||
|
var op2Prec = KnownOperators.GetPrecedence(op2.OperatorType);
|
||||||
|
var op1IsLeft = KnownOperators.IsLeftAssociative(inToken.OperatorType);
|
||||||
|
|
||||||
|
if ((op1IsLeft && op1Prec <= op2Prec) ||
|
||||||
|
(!op1IsLeft && op1Prec < op2Prec))
|
||||||
|
{
|
||||||
|
_output.Add(_stack.Pop());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_stack.Push(inToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCloseParenthesis()
|
||||||
|
{
|
||||||
|
bool closed = false;
|
||||||
|
while (_stack.Count > 0)
|
||||||
|
{
|
||||||
|
var pop = _stack.Pop();
|
||||||
|
if (pop.TokenType == ExpressionTokenType.OpenParenthesis)
|
||||||
|
{
|
||||||
|
closed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_output.Add(pop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!closed)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(UnbalancedParenMsg);
|
||||||
|
}
|
||||||
|
else if (_stack.Count > 0)
|
||||||
|
{
|
||||||
|
var next = _stack.Peek();
|
||||||
|
if (next != null && next.TokenType == ExpressionTokenType.Function)
|
||||||
|
{
|
||||||
|
_output.Add(_stack.Pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/InfixTokenizer.cs
Normal file
206
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/InfixTokenizer.cs
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser.Parsing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A tokenizer that parses an input expression string in infix notation into tokens without white spaces.
|
||||||
|
/// </summary>
|
||||||
|
public class InfixTokenizer : IExpressionTokenizer
|
||||||
|
{
|
||||||
|
List<ExpressionToken> _currentTokens;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits the specified input into a list of <see cref="ExpressionToken" /> values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The input.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.NotSupportedException"></exception>
|
||||||
|
public ExpressionToken[] Tokenize(string input)
|
||||||
|
{
|
||||||
|
_currentTokens = new List<ExpressionToken>();
|
||||||
|
ExpressionToken lastExpToken = null;
|
||||||
|
|
||||||
|
var reader = new ListReader<RawToken>(new RawTokenizer().Tokenize(input));
|
||||||
|
|
||||||
|
while (!reader.IsEnd)
|
||||||
|
{
|
||||||
|
var curRawToken = reader.Read();
|
||||||
|
switch (curRawToken.TokenType)
|
||||||
|
{
|
||||||
|
case RawTokenType.WhiteSpace:
|
||||||
|
// generially ends previous token outside other special scopes
|
||||||
|
lastExpToken = null;
|
||||||
|
break;
|
||||||
|
case RawTokenType.Literal:
|
||||||
|
if (lastExpToken == null || lastExpToken.TokenType != ExpressionTokenType.Value)
|
||||||
|
{
|
||||||
|
lastExpToken = new ExpressionToken { TokenType = ExpressionTokenType.Value };
|
||||||
|
_currentTokens.Add(lastExpToken);
|
||||||
|
}
|
||||||
|
lastExpToken.Append(curRawToken);
|
||||||
|
break;
|
||||||
|
case RawTokenType.Symbol:
|
||||||
|
// first do operator match by checking the prev op
|
||||||
|
// and see if combined with current token would still match a known operator
|
||||||
|
if (KnownOperators.IsKnown(curRawToken.Value))
|
||||||
|
{
|
||||||
|
if (lastExpToken != null && lastExpToken.TokenType == ExpressionTokenType.Operator)
|
||||||
|
{
|
||||||
|
var testOpValue = lastExpToken.Value + curRawToken.Value;
|
||||||
|
if (KnownOperators.IsKnown(testOpValue))
|
||||||
|
{
|
||||||
|
// just append it
|
||||||
|
lastExpToken.Append(curRawToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// start new one
|
||||||
|
lastExpToken = new ExpressionToken { TokenType = ExpressionTokenType.Operator };
|
||||||
|
_currentTokens.Add(lastExpToken);
|
||||||
|
lastExpToken.Append(curRawToken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lastExpToken = HandleNonOperatorSymbolToken(reader, lastExpToken, curRawToken);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// should never happen
|
||||||
|
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Unsupported token type {0} at position {1}.", curRawToken.TokenType, curRawToken.Position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MassageTokens(_currentTokens);
|
||||||
|
|
||||||
|
return _currentTokens.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpressionToken HandleNonOperatorSymbolToken(ListReader<RawToken> reader, ExpressionToken lastExpToken, RawToken curRawToken)
|
||||||
|
{
|
||||||
|
switch (curRawToken.Value)
|
||||||
|
{
|
||||||
|
case ",":
|
||||||
|
lastExpToken = new ExpressionToken { TokenType = ExpressionTokenType.Comma };
|
||||||
|
_currentTokens.Add(lastExpToken);
|
||||||
|
lastExpToken.Append(curRawToken);
|
||||||
|
break;
|
||||||
|
case "(":
|
||||||
|
// if last one is string make it a function
|
||||||
|
if (lastExpToken != null && lastExpToken.TokenType == ExpressionTokenType.Value)
|
||||||
|
{
|
||||||
|
lastExpToken.TokenType = ExpressionTokenType.Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastExpToken = new ExpressionToken { TokenType = ExpressionTokenType.OpenParenthesis };
|
||||||
|
_currentTokens.Add(lastExpToken);
|
||||||
|
lastExpToken.Append(curRawToken);
|
||||||
|
break;
|
||||||
|
case ")":
|
||||||
|
lastExpToken = new ExpressionToken { TokenType = ExpressionTokenType.CloseParenthesis };
|
||||||
|
_currentTokens.Add(lastExpToken);
|
||||||
|
lastExpToken.Append(curRawToken);
|
||||||
|
break;
|
||||||
|
case "{":
|
||||||
|
// read until end of }
|
||||||
|
lastExpToken = ReadToLiteralAs(reader, "}", ExpressionTokenType.Field);
|
||||||
|
break;
|
||||||
|
case "\"":
|
||||||
|
// read until end of "
|
||||||
|
lastExpToken = ReadToLiteralAs(reader, "\"", ExpressionTokenType.DoubleQuoted);
|
||||||
|
break;
|
||||||
|
case "'":
|
||||||
|
// read until end of '
|
||||||
|
lastExpToken = ReadToLiteralAs(reader, "'", ExpressionTokenType.SingleQuoted);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastExpToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpressionToken ReadToLiteralAs(ListReader<RawToken> reader, string literalValue, ExpressionTokenType tokenType)
|
||||||
|
{
|
||||||
|
ExpressionToken lastExpToken = new ExpressionToken { TokenType = tokenType };
|
||||||
|
_currentTokens.Add(lastExpToken);
|
||||||
|
while (!reader.IsEnd)
|
||||||
|
{
|
||||||
|
var next = reader.Read();
|
||||||
|
if (next.TokenType == RawTokenType.Symbol && next.Value == literalValue)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastExpToken.Append(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastExpToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MassageTokens(List<ExpressionToken> tokens)
|
||||||
|
{
|
||||||
|
// do final token parsing based on contexts and cleanup
|
||||||
|
|
||||||
|
var reader = new ListReader<ExpressionToken>(tokens);
|
||||||
|
while (!reader.IsEnd)
|
||||||
|
{
|
||||||
|
var tk = reader.Read();
|
||||||
|
|
||||||
|
if (tk.TokenType == ExpressionTokenType.Operator)
|
||||||
|
{
|
||||||
|
// special detection for operators depending on where it is :(
|
||||||
|
DetermineOperatorType(reader, tk);
|
||||||
|
}
|
||||||
|
|
||||||
|
tk.Freeze();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DetermineOperatorType(ListReader<ExpressionToken> reader, ExpressionToken tk)
|
||||||
|
{
|
||||||
|
tk.OperatorType = KnownOperators.TryMap(tk.Value);
|
||||||
|
switch (tk.OperatorType)
|
||||||
|
{
|
||||||
|
case OperatorType.PreDecrement:
|
||||||
|
case OperatorType.PreIncrement:
|
||||||
|
// detect if it's really post ++ -- versions
|
||||||
|
var prev = reader.Position > 1 ? reader.Peek(-2) : null;
|
||||||
|
if (prev != null && prev.TokenType == ExpressionTokenType.Value)
|
||||||
|
{
|
||||||
|
if (tk.OperatorType == OperatorType.PreIncrement)
|
||||||
|
{
|
||||||
|
tk.OperatorType = OperatorType.PostIncrement;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tk.OperatorType = OperatorType.PostDecrement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperatorType.Addition:
|
||||||
|
case OperatorType.Subtraction:
|
||||||
|
// detect if unary + -
|
||||||
|
prev = reader.Position > 1 ? reader.Peek(-2) : null;
|
||||||
|
if (prev == null ||
|
||||||
|
(prev.TokenType == ExpressionTokenType.Operator &&
|
||||||
|
prev.OperatorType != OperatorType.PostDecrement &&
|
||||||
|
prev.OperatorType != OperatorType.PostIncrement))
|
||||||
|
{
|
||||||
|
if (tk.OperatorType == OperatorType.Addition)
|
||||||
|
{
|
||||||
|
tk.OperatorType = OperatorType.UnaryPlus;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tk.OperatorType = OperatorType.UnaryMinus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OperatorType.None:
|
||||||
|
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Operator {0} is not supported.", tk.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
146
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/KnownOperators.cs
Normal file
146
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/KnownOperators.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser.Parsing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains recognized operator info.
|
||||||
|
/// </summary>
|
||||||
|
public static class KnownOperators
|
||||||
|
{
|
||||||
|
static readonly Dictionary<string, OperatorType> DefaultMap = new Dictionary<string, OperatorType>
|
||||||
|
{
|
||||||
|
// double char
|
||||||
|
{"++", OperatorType.PreIncrement },
|
||||||
|
{"--", OperatorType.PreDecrement },
|
||||||
|
{"+=", OperatorType.AdditionAssignment },
|
||||||
|
{"-=", OperatorType.SubtractionAssignment },
|
||||||
|
{"*=", OperatorType.MultiplicationAssignment },
|
||||||
|
{"/=", OperatorType.DivisionAssignment },
|
||||||
|
{"%=", OperatorType.ModulusAssignment },
|
||||||
|
{"==", OperatorType.Equal },
|
||||||
|
{"!=", OperatorType.NotEqual},
|
||||||
|
{"<=", OperatorType.LessThanOrEqual },
|
||||||
|
{">=", OperatorType.GreaterThanOrEqual },
|
||||||
|
{"&&", OperatorType.LogicalAnd },
|
||||||
|
{"||", OperatorType.LogicalOr },
|
||||||
|
|
||||||
|
// single char
|
||||||
|
{"+", OperatorType.Addition },
|
||||||
|
{"-", OperatorType.Subtraction },
|
||||||
|
{"*", OperatorType.Multiplication },
|
||||||
|
{"/", OperatorType.Division },
|
||||||
|
{"=", OperatorType.Assignment },
|
||||||
|
{"%", OperatorType.Modulus },
|
||||||
|
//"^",
|
||||||
|
{"<", OperatorType.LessThan },
|
||||||
|
{">", OperatorType.GreaterThan },
|
||||||
|
//"~",
|
||||||
|
{"&", OperatorType.BitwiseAnd },
|
||||||
|
{"|", OperatorType.BitwiseOr },
|
||||||
|
{"!", OperatorType.LogicalNegation },
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified operator value is recognized.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="operatorValue">The operator value.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsKnown(string operatorValue)
|
||||||
|
{
|
||||||
|
return DefaultMap.ContainsKey(operatorValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to get the enum version of the operator string value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="operatorValue">The operator value.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static OperatorType TryMap(string operatorValue)
|
||||||
|
{
|
||||||
|
if (DefaultMap.ContainsKey(operatorValue))
|
||||||
|
{
|
||||||
|
return DefaultMap[operatorValue];
|
||||||
|
}
|
||||||
|
return OperatorType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the precedence of an operator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int GetPrecedence(OperatorType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case OperatorType.PostDecrement:
|
||||||
|
case OperatorType.PostIncrement:
|
||||||
|
return 100;
|
||||||
|
case OperatorType.PreDecrement:
|
||||||
|
case OperatorType.PreIncrement:
|
||||||
|
case OperatorType.UnaryMinus:
|
||||||
|
case OperatorType.UnaryPlus:
|
||||||
|
case OperatorType.LogicalNegation:
|
||||||
|
return 90;
|
||||||
|
case OperatorType.Multiplication:
|
||||||
|
case OperatorType.Division:
|
||||||
|
case OperatorType.Modulus:
|
||||||
|
return 85;
|
||||||
|
case OperatorType.Addition:
|
||||||
|
case OperatorType.Subtraction:
|
||||||
|
return 80;
|
||||||
|
case OperatorType.LessThan:
|
||||||
|
case OperatorType.LessThanOrEqual:
|
||||||
|
case OperatorType.GreaterThan:
|
||||||
|
case OperatorType.GreaterThanOrEqual:
|
||||||
|
return 75;
|
||||||
|
case OperatorType.Equal:
|
||||||
|
case OperatorType.NotEqual:
|
||||||
|
return 70;
|
||||||
|
case OperatorType.BitwiseAnd:
|
||||||
|
case OperatorType.BitwiseOr:
|
||||||
|
return 65;
|
||||||
|
case OperatorType.LogicalAnd:
|
||||||
|
case OperatorType.LogicalOr:
|
||||||
|
return 60;
|
||||||
|
case OperatorType.Assignment:
|
||||||
|
case OperatorType.AdditionAssignment:
|
||||||
|
case OperatorType.DivisionAssignment:
|
||||||
|
case OperatorType.ModulusAssignment:
|
||||||
|
case OperatorType.MultiplicationAssignment:
|
||||||
|
case OperatorType.SubtractionAssignment:
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the operator is left-to-right associative (true) or right-to-left (false).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsLeftAssociative(OperatorType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case OperatorType.PreDecrement:
|
||||||
|
case OperatorType.PreIncrement:
|
||||||
|
case OperatorType.UnaryMinus:
|
||||||
|
case OperatorType.UnaryPlus:
|
||||||
|
case OperatorType.LogicalNegation:
|
||||||
|
case OperatorType.Assignment:
|
||||||
|
case OperatorType.AdditionAssignment:
|
||||||
|
case OperatorType.DivisionAssignment:
|
||||||
|
case OperatorType.ModulusAssignment:
|
||||||
|
case OperatorType.MultiplicationAssignment:
|
||||||
|
case OperatorType.SubtractionAssignment:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
89
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/ListReader.cs
Normal file
89
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/ListReader.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser.Parsing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A simple reader for an IList.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TItem">The type of the item in the list.</typeparam>
|
||||||
|
public class ListReader<TItem>
|
||||||
|
{
|
||||||
|
IList<TItem> _list;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ListReader{TItem}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="list">The list to read.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">list</exception>
|
||||||
|
public ListReader(IList<TItem> list)
|
||||||
|
{
|
||||||
|
if (list == null) { throw new ArgumentNullException("list"); }
|
||||||
|
|
||||||
|
_list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _position;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the position of the reader. This is the 0-based index.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The position.
|
||||||
|
/// </value>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
|
||||||
|
public int Position
|
||||||
|
{
|
||||||
|
get { return _position; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < 0 || value > _list.Count)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("value");
|
||||||
|
}
|
||||||
|
_position = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the reader has reached the end of list.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if this instance is eol; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public bool IsEnd { get { return _position >= _list.Count; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the current item in the list and moves the <see cref="Position" /> forward.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
|
||||||
|
public TItem Read()
|
||||||
|
{
|
||||||
|
return _list[Position++];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Peeks the current item in the list without moving the <see cref="Position"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
|
||||||
|
public TItem Peek()
|
||||||
|
{
|
||||||
|
return Peek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Peeks the item in the list without moving the <see cref="Position" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset from current position.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
|
||||||
|
public TItem Peek(int offset)
|
||||||
|
{
|
||||||
|
// let list throw the exception.
|
||||||
|
return _list[Position + offset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/OperatorType.cs
Normal file
131
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/OperatorType.cs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser.Parsing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates the recognized operator types.
|
||||||
|
/// </summary>
|
||||||
|
public enum OperatorType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unspecified default value.
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
/// <summary>
|
||||||
|
/// ++ after a value.
|
||||||
|
/// </summary>
|
||||||
|
PostIncrement,
|
||||||
|
/// <summary>
|
||||||
|
/// -- after a value.
|
||||||
|
/// </summary>
|
||||||
|
PostDecrement,
|
||||||
|
/// <summary>
|
||||||
|
/// ++ before a value.
|
||||||
|
/// </summary>
|
||||||
|
PreIncrement,
|
||||||
|
/// <summary>
|
||||||
|
/// -- before a value.
|
||||||
|
/// </summary>
|
||||||
|
PreDecrement,
|
||||||
|
/// <summary>
|
||||||
|
/// + before a value.
|
||||||
|
/// </summary>
|
||||||
|
UnaryPlus,
|
||||||
|
/// <summary>
|
||||||
|
/// - before a value.
|
||||||
|
/// </summary>
|
||||||
|
UnaryMinus,
|
||||||
|
/// <summary>
|
||||||
|
/// ! before a value.
|
||||||
|
/// </summary>
|
||||||
|
LogicalNegation,
|
||||||
|
/// <summary>
|
||||||
|
/// * between values.
|
||||||
|
/// </summary>
|
||||||
|
Multiplication,
|
||||||
|
/// <summary>
|
||||||
|
/// / between values.
|
||||||
|
/// </summary>
|
||||||
|
Division,
|
||||||
|
/// <summary>
|
||||||
|
/// % between values.
|
||||||
|
/// </summary>
|
||||||
|
Modulus,
|
||||||
|
/// <summary>
|
||||||
|
/// + between values.
|
||||||
|
/// </summary>
|
||||||
|
Addition,
|
||||||
|
/// <summary>
|
||||||
|
/// - between values.
|
||||||
|
/// </summary>
|
||||||
|
Subtraction,
|
||||||
|
/// <summary>
|
||||||
|
/// < between values.
|
||||||
|
/// </summary>
|
||||||
|
LessThan,
|
||||||
|
/// <summary>
|
||||||
|
/// <= between values.
|
||||||
|
/// </summary>
|
||||||
|
LessThanOrEqual,
|
||||||
|
/// <summary>
|
||||||
|
/// > between values.
|
||||||
|
/// </summary>
|
||||||
|
GreaterThan,
|
||||||
|
/// <summary>
|
||||||
|
/// >= between values.
|
||||||
|
/// </summary>
|
||||||
|
GreaterThanOrEqual,
|
||||||
|
/// <summary>
|
||||||
|
/// == between values.
|
||||||
|
/// </summary>
|
||||||
|
Equal,
|
||||||
|
/// <summary>
|
||||||
|
/// != between values.
|
||||||
|
/// </summary>
|
||||||
|
NotEqual,
|
||||||
|
/// <summary>
|
||||||
|
/// & between values.
|
||||||
|
/// </summary>
|
||||||
|
BitwiseAnd,
|
||||||
|
/// <summary>
|
||||||
|
/// | between values.
|
||||||
|
/// </summary>
|
||||||
|
BitwiseOr,
|
||||||
|
/// <summary>
|
||||||
|
/// && between values.
|
||||||
|
/// </summary>
|
||||||
|
LogicalAnd,
|
||||||
|
/// <summary>
|
||||||
|
/// || between values.
|
||||||
|
/// </summary>
|
||||||
|
LogicalOr,
|
||||||
|
/// <summary>
|
||||||
|
/// = between values.
|
||||||
|
/// </summary>
|
||||||
|
Assignment,
|
||||||
|
/// <summary>
|
||||||
|
/// += between values.
|
||||||
|
/// </summary>
|
||||||
|
AdditionAssignment,
|
||||||
|
/// <summary>
|
||||||
|
/// -= between values.
|
||||||
|
/// </summary>
|
||||||
|
SubtractionAssignment,
|
||||||
|
/// <summary>
|
||||||
|
/// *= between values.
|
||||||
|
/// </summary>
|
||||||
|
MultiplicationAssignment,
|
||||||
|
/// <summary>
|
||||||
|
/// /= between values.
|
||||||
|
/// </summary>
|
||||||
|
DivisionAssignment,
|
||||||
|
/// <summary>
|
||||||
|
/// %= between values.
|
||||||
|
/// </summary>
|
||||||
|
ModulusAssignment,
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
95
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/RawToken.cs
Normal file
95
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/RawToken.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser.Parsing
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A low-level token split from the initial text input.
|
||||||
|
/// </summary>
|
||||||
|
public class RawToken
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RawToken"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <param name="position">The position.</param>
|
||||||
|
internal RawToken(RawTokenType type, int position)
|
||||||
|
{
|
||||||
|
TokenType = type;
|
||||||
|
Position = position;
|
||||||
|
ValueBuilder = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the token type.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The type.
|
||||||
|
/// </value>
|
||||||
|
public RawTokenType TokenType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the starting position of this token in the original input.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The position.
|
||||||
|
/// </value>
|
||||||
|
public int Position { get; private set; }
|
||||||
|
|
||||||
|
// TODO: test pef on using builder or using string directly
|
||||||
|
internal StringBuilder ValueBuilder { get; private set; }
|
||||||
|
|
||||||
|
internal void Append(RawToken token)
|
||||||
|
{
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
ValueBuilder.Append(token.ValueBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the token value.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The value.
|
||||||
|
/// </value>
|
||||||
|
public string Value { get { return ValueBuilder.ToString(); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="System.String" /> that represents this instance.
|
||||||
|
/// </returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates the low-level token type.
|
||||||
|
/// </summary>
|
||||||
|
public enum RawTokenType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid token type.
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
/// <summary>
|
||||||
|
/// Token is white space.
|
||||||
|
/// </summary>
|
||||||
|
WhiteSpace,
|
||||||
|
/// <summary>
|
||||||
|
/// Token is a symbol.
|
||||||
|
/// </summary>
|
||||||
|
Symbol,
|
||||||
|
/// <summary>
|
||||||
|
/// Token is not symbol or white space.
|
||||||
|
/// </summary>
|
||||||
|
Literal,
|
||||||
|
}
|
||||||
|
}
|
102
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/RawTokenizer.cs
Normal file
102
DunGenPlus/DunGenPlus/ExpressionParser/Parsing/RawTokenizer.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Soukoku.ExpressionParser.Parsing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A low-level tokenizer that parses an input expression string into tokens.
|
||||||
|
/// </summary>
|
||||||
|
public class RawTokenizer
|
||||||
|
{
|
||||||
|
static readonly char[] DefaultSymbols = new[]
|
||||||
|
{
|
||||||
|
'+', '-', '*', '/', '=', '%', '^',
|
||||||
|
',', '<', '>', '&', '|', '!',
|
||||||
|
'(', ')', '{', '}', '[', ']',
|
||||||
|
'"', '\'', '~'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RawTokenizer"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public RawTokenizer() : this(null) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RawTokenizer" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="symbols">The char values to count as symbols. If null the <see cref="DefaultSymbols"/> will be used.</param>
|
||||||
|
public RawTokenizer(params char[] symbols)
|
||||||
|
{
|
||||||
|
_symbols = symbols ?? DefaultSymbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] _symbols;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the char values that count as symbols for this tokenizer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The symbols.
|
||||||
|
/// </value>
|
||||||
|
public char[] GetSymbols() { return (char[])_symbols.Clone(); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits the specified input into a list of <see cref="RawToken"/> values using white space and symbols.
|
||||||
|
/// The tokens can be recombined to rebuild the original input exactly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The input.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public RawToken[] Tokenize(string input)
|
||||||
|
{
|
||||||
|
var tokens = new List<RawToken>();
|
||||||
|
|
||||||
|
if (input != null)
|
||||||
|
{
|
||||||
|
RawToken lastToken = null;
|
||||||
|
for (int i = 0; i < input.Length; i++)
|
||||||
|
{
|
||||||
|
var ch = input[i];
|
||||||
|
if (char.IsWhiteSpace(ch))
|
||||||
|
{
|
||||||
|
lastToken = NewTokenIfNecessary(tokens, lastToken, RawTokenType.WhiteSpace, i);
|
||||||
|
}
|
||||||
|
else if (_symbols.Contains(ch))
|
||||||
|
{
|
||||||
|
lastToken = NewTokenIfNecessary(tokens, lastToken, RawTokenType.Symbol, i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lastToken = NewTokenIfNecessary(tokens, lastToken, RawTokenType.Literal, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '\\' && ++i < input.Length)
|
||||||
|
{
|
||||||
|
// assume escape and just append next char as-is
|
||||||
|
var next = input[i];
|
||||||
|
lastToken.ValueBuilder.Append(next);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lastToken.ValueBuilder.Append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static RawToken NewTokenIfNecessary(List<RawToken> tokens, RawToken lastToken, RawTokenType curTokenType, int position)
|
||||||
|
{
|
||||||
|
if (lastToken == null || lastToken.TokenType != curTokenType ||
|
||||||
|
curTokenType == RawTokenType.Symbol) // for symbol always let it be by itself
|
||||||
|
{
|
||||||
|
lastToken = new RawToken(curTokenType, position);
|
||||||
|
tokens.Add(lastToken);
|
||||||
|
}
|
||||||
|
return lastToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
DunGenPlus/DunGenPlus/ExpressionParser/ValueTypeHint.cs
Normal file
17
DunGenPlus/DunGenPlus/ExpressionParser/ValueTypeHint.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Soukoku.ExpressionParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to indicate how to handle resolve field value.
|
||||||
|
/// </summary>
|
||||||
|
public enum ValueTypeHint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Value is converted to suitable type for comparison purposes.
|
||||||
|
/// </summary>
|
||||||
|
Auto,
|
||||||
|
/// <summary>
|
||||||
|
/// Value is forced to be text for comparison purposes.
|
||||||
|
/// </summary>
|
||||||
|
Text,
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ using UnityEngine.Rendering.HighDefinition;
|
|||||||
using BepInEx.Logging;
|
using BepInEx.Logging;
|
||||||
using DunGenPlus.DevTools;
|
using DunGenPlus.DevTools;
|
||||||
using DunGenPlus.Patches;
|
using DunGenPlus.Patches;
|
||||||
|
using DunGenPlus.DevTools.Panels;
|
||||||
|
|
||||||
[assembly: SecurityPermission( SecurityAction.RequestMinimum, SkipVerification = true )]
|
[assembly: SecurityPermission( SecurityAction.RequestMinimum, SkipVerification = true )]
|
||||||
namespace DunGenPlus.Generation {
|
namespace DunGenPlus.Generation {
|
||||||
@ -36,7 +37,7 @@ namespace DunGenPlus.Generation {
|
|||||||
ActiveAlternative = true;
|
ActiveAlternative = true;
|
||||||
|
|
||||||
var props = extender.Properties.Copy(extender.Version);
|
var props = extender.Properties.Copy(extender.Version);
|
||||||
var callback = new EventCallbackScenario(DevDebugManager.Instance);
|
var callback = new EventCallbackScenario(DunGenPlusPanel.Instance && DunGenPlusPanel.Instance.eventCallbackValue);
|
||||||
Instance.Events.OnModifyDunGenExtenderProperties.Invoke(props, callback);
|
Instance.Events.OnModifyDunGenExtenderProperties.Invoke(props, callback);
|
||||||
props.NormalNodeArchetypesProperties.SetupProperties(generator);
|
props.NormalNodeArchetypesProperties.SetupProperties(generator);
|
||||||
Properties = props;
|
Properties = props;
|
||||||
|
@ -1,36 +1,92 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using DunGen;
|
||||||
using DunGen.Adapters;
|
using DunGen.Adapters;
|
||||||
using DunGenPlus.Components;
|
using DunGenPlus.Components;
|
||||||
|
using DunGenPlus.Components.Scripting;
|
||||||
using DunGenPlus.Generation;
|
using DunGenPlus.Generation;
|
||||||
using DunGenPlus.Utils;
|
using DunGenPlus.Utils;
|
||||||
|
using static DunGenPlus.Managers.DoorwayManager;
|
||||||
|
|
||||||
namespace DunGenPlus.Managers {
|
namespace DunGenPlus.Managers {
|
||||||
public static class DoorwayManager {
|
public static class DoorwayManager {
|
||||||
|
|
||||||
public static ActionList onMainEntranceTeleportSpawnedEvent = new ActionList("onMainEntranceTeleportSpawned");
|
public static ActionList onMainEntranceTeleportSpawnedEvent = new ActionList("onMainEntranceTeleportSpawned");
|
||||||
public static List<DoorwayCleanup> doorwayCleanupList;
|
//public static List<DoorwayCleanup> doorwayCleanupList;
|
||||||
|
|
||||||
|
public class Scripts {
|
||||||
|
public List<IDunGenScriptingParent> scriptList;
|
||||||
|
public List<Action> actionList;
|
||||||
|
|
||||||
|
public Scripts(){
|
||||||
|
scriptList = new List<IDunGenScriptingParent>();
|
||||||
|
actionList = new List<Action>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(IDunGenScriptingParent script) {
|
||||||
|
scriptList.Add(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(Action action) {
|
||||||
|
actionList.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Call(){
|
||||||
|
foreach(var s in scriptList){
|
||||||
|
s.Call();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var a in actionList){
|
||||||
|
a.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptList.Count + actionList.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<DunGenScriptingHook, Scripts> scriptingLists;
|
||||||
|
|
||||||
public static void ResetList(){
|
public static void ResetList(){
|
||||||
doorwayCleanupList = new List<DoorwayCleanup>();
|
//doorwayCleanupList = new List<DoorwayCleanup>();
|
||||||
|
scriptingLists = new Dictionary<DunGenScriptingHook, Scripts>();
|
||||||
|
foreach(DunGenScriptingHook e in Enum.GetValues(typeof(DunGenScriptingHook))){
|
||||||
|
scriptingLists.Add(e, new Scripts());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddDoorwayCleanup(DoorwayCleanup cleanup){
|
public static void AddDoorwayCleanup(DoorwayCleanup cleanup){
|
||||||
doorwayCleanupList.Add(cleanup);
|
//doorwayCleanupList.Add(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void onMainEntranceTeleportSpawnedFunction(){
|
public static void AddDunGenScriptHook(IDunGenScriptingParent script){
|
||||||
|
scriptingLists[script.GetScriptingHook].Add(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddActionHook(DunGenScriptingHook hook, Action action){
|
||||||
|
scriptingLists[hook].Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnMainEntranceTeleportSpawnedFunction(){
|
||||||
if (DunGenPlusGenerator.Active) {
|
if (DunGenPlusGenerator.Active) {
|
||||||
foreach(var d in doorwayCleanupList){
|
|
||||||
d.SetBlockers(false);
|
//foreach(var d in doorwayCleanupList){
|
||||||
d.Cleanup();
|
// d.SetBlockers(false);
|
||||||
|
// d.Cleanup();
|
||||||
|
// Plugin.logger.LogWarning(d.GetComponentInParent<Tile>().gameObject.name);
|
||||||
|
//}
|
||||||
|
|
||||||
|
var anyFunctionCalled = false;
|
||||||
|
foreach(var d in scriptingLists.Values){
|
||||||
|
anyFunctionCalled = anyFunctionCalled | d.Call();
|
||||||
}
|
}
|
||||||
|
|
||||||
// we can leave early if doorway cleanup is not used (most likely for most dungeons anyway)
|
// we can leave early if doorway cleanup is not used (most likely for most dungeons anyway)
|
||||||
if (doorwayCleanupList.Count == 0) return;
|
if (!anyFunctionCalled) return;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
var dungeonGen = RoundManager.Instance.dungeonGenerator;
|
var dungeonGen = RoundManager.Instance.dungeonGenerator;
|
||||||
@ -45,5 +101,11 @@ namespace DunGenPlus.Managers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetLevelObjectVariablesFunction(){
|
||||||
|
if (DunGenPlusGenerator.Active) {
|
||||||
|
scriptingLists[DunGenScriptingHook.SetLevelObjectVariables ].Call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,5 +69,12 @@ namespace DunGenPlus.Patches {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPriority(Priority.First)]
|
||||||
|
[HarmonyPatch(typeof(RoundManager), "SetLevelObjectVariables")]
|
||||||
|
public static void SetLevelObjectVariablesPatch (ref RoundManager __instance) {
|
||||||
|
DoorwayManager.SetLevelObjectVariablesFunction();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using BepInEx.Logging;
|
|||||||
using DunGen;
|
using DunGen;
|
||||||
using DunGen.Graph;
|
using DunGen.Graph;
|
||||||
using DunGenPlus.Collections;
|
using DunGenPlus.Collections;
|
||||||
|
using DunGenPlus.Components.Scripting;
|
||||||
using DunGenPlus.Generation;
|
using DunGenPlus.Generation;
|
||||||
using DunGenPlus.Managers;
|
using DunGenPlus.Managers;
|
||||||
using DunGenPlus.Patches;
|
using DunGenPlus.Patches;
|
||||||
@ -26,7 +27,7 @@ namespace DunGenPlus {
|
|||||||
|
|
||||||
internal const string modGUID = "dev.ladyalice.dungenplus";
|
internal const string modGUID = "dev.ladyalice.dungenplus";
|
||||||
private const string modName = "Dungeon Generation Plus";
|
private const string modName = "Dungeon Generation Plus";
|
||||||
private const string modVersion = "1.3.4";
|
private const string modVersion = "1.4.0";
|
||||||
|
|
||||||
internal readonly Harmony Harmony = new Harmony(modGUID);
|
internal readonly Harmony Harmony = new Harmony(modGUID);
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ namespace DunGenPlus {
|
|||||||
|
|
||||||
Assets.LoadAssets();
|
Assets.LoadAssets();
|
||||||
Assets.LoadAssetBundle();
|
Assets.LoadAssetBundle();
|
||||||
DoorwayManager.onMainEntranceTeleportSpawnedEvent.AddEvent("DoorwayCleanup", DoorwayManager.onMainEntranceTeleportSpawnedFunction);
|
DoorwayManager.onMainEntranceTeleportSpawnedEvent.AddEvent("DoorwayCleanup", DoorwayManager.OnMainEntranceTeleportSpawnedFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
using System;
|
using BepInEx.Logging;
|
||||||
|
using DunGenPlus.Components.Scripting;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
|
|
||||||
namespace DunGenPlus.Utils {
|
namespace DunGenPlus.Utils {
|
||||||
@ -52,4 +55,26 @@ namespace DunGenPlus.Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Utility {
|
||||||
|
|
||||||
|
public static void PrintLog(string message, LogLevel logLevel){
|
||||||
|
if (DunGenPlusScript.InDebugMode){
|
||||||
|
switch(logLevel){
|
||||||
|
case LogLevel.Error:
|
||||||
|
case LogLevel.Fatal:
|
||||||
|
Debug.LogError(message);
|
||||||
|
break;
|
||||||
|
case LogLevel.Warning:
|
||||||
|
Debug.LogWarning(message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Debug.Log(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Plugin.logger.Log(logLevel, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -73,10 +73,12 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="DunGenExtenderPropertyDrawer.cs" />
|
<Compile Include="DunGenExtenderPropertyDrawer.cs" />
|
||||||
|
<Compile Include="NamedGameObjectReferencePropertyDrawer.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="PropertyDrawerUtility.cs" />
|
<Compile Include="PropertyDrawerUtility.cs" />
|
||||||
<Compile Include="PropertyOverridePropertyDrawer.cs" />
|
<Compile Include="PropertyOverridePropertyDrawer.cs" />
|
||||||
<Compile Include="ReadOnlyPropertyDrawer.cs" />
|
<Compile Include="ReadOnlyPropertyDrawer.cs" />
|
||||||
|
<Compile Include="ScriptActionPropertyDrawer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using DunGenPlus;
|
||||||
|
using DunGenPlus.Collections;
|
||||||
|
using DunGenPlus.Components.Scripting;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
|
||||||
|
namespace DunGenPlusEditor {
|
||||||
|
|
||||||
|
[CustomPropertyDrawer(typeof(NamedGameObjectReference))]
|
||||||
|
public class NamedGameObjectReferencePropertyDrawer : PropertyDrawer {
|
||||||
|
public override VisualElement CreatePropertyGUI(SerializedProperty property) {
|
||||||
|
|
||||||
|
var container = new VisualElement();
|
||||||
|
container.Add(new PropertyField(property.FindPropertyRelative("name")));
|
||||||
|
container.Add(new PropertyField(property.FindPropertyRelative("gameObjects")));
|
||||||
|
container.Add(new PropertyField(property.FindPropertyRelative("overrideState")));
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
DunGenPlus/DunGenPlusEditor/ScriptActionPropertyDrawer.cs
Normal file
43
DunGenPlus/DunGenPlusEditor/ScriptActionPropertyDrawer.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using DunGenPlus;
|
||||||
|
using DunGenPlus.Collections;
|
||||||
|
using DunGenPlus.Components.Scripting;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
|
||||||
|
namespace DunGenPlusEditor {
|
||||||
|
|
||||||
|
[CustomPropertyDrawer(typeof(ScriptAction))]
|
||||||
|
public class ScriptActionPropertyDrawer : PropertyDrawer {
|
||||||
|
public override VisualElement CreatePropertyGUI(SerializedProperty property) {
|
||||||
|
|
||||||
|
var container = new VisualElement();
|
||||||
|
var typeProperty = property.FindPropertyRelative("type");
|
||||||
|
container.Add(new PropertyField(typeProperty));
|
||||||
|
|
||||||
|
switch((ScriptActionType)typeProperty.intValue){
|
||||||
|
case ScriptActionType.SetNamedReferenceState:
|
||||||
|
AddPropertyFields(container, property, ("namedReference", "Named Reference"), ("boolValue", "State"));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
container.Add(new PropertyField(property.FindPropertyRelative("overrideState")));
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddPropertyFields(VisualElement container, SerializedProperty property, params (string field, string label)[] pairs){
|
||||||
|
foreach(var pair in pairs){
|
||||||
|
container.Add(new PropertyField(property.FindPropertyRelative(pair.field), pair.label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user