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 {
|
||||
public class DoorwayCleanup : MonoBehaviour, IDungeonCompleteReceiver {
|
||||
|
||||
[Header("OBSOLUTE. Please use DoorwayScriptingParent")]
|
||||
[Header("Doorway References")]
|
||||
[Tooltip("The doorway reference.")]
|
||||
public Doorway doorway;
|
||||
|
@ -6,6 +6,8 @@ using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||
|
||||
[Obsolete("Please use DoorwayScriptingParent")]
|
||||
public class DCSConnectorBlockerSpawnedPrefab : DoorwayCleanupScript {
|
||||
|
||||
public enum Action { SwitchToConnector, SwitchToBlocker };
|
||||
|
@ -6,17 +6,20 @@ using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||
|
||||
[Obsolete("Please use DoorwayScriptingParent")]
|
||||
public class DCSRemoveDoorwayConnectedDoorway : DoorwayCleanupScriptDoorwayCompare {
|
||||
|
||||
[Header("Removes Doorway Gameobject\nif the neighboring doorway's priority matches the operation comparison")]
|
||||
[Header("Operation Comparison")]
|
||||
public int doorwayPriority;
|
||||
public int doorwayPriorityB;
|
||||
public Operation operation = Operation.Equal;
|
||||
|
||||
public override void Cleanup(DoorwayCleanup parent) {
|
||||
var doorway = parent.doorway;
|
||||
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) {
|
||||
parent.SwitchDoorwayGameObject(false);
|
||||
|
@ -6,6 +6,8 @@ using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||
|
||||
[Obsolete("Please use DoorwayScriptingParent")]
|
||||
public class DCSRemoveDoorwaySpawnedPrefab : DoorwayCleanupScript {
|
||||
|
||||
[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;
|
||||
|
||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||
[Obsolete("Please use DoorwayScriptingParent")]
|
||||
public class DCSRemoveGameObjectsConnectedDoorway : DoorwayCleanupScriptDoorwayCompare {
|
||||
|
||||
[Header("Removes target GameObjects\nif the neighboring doorway's priority matches the operation comparison")]
|
||||
|
||||
[Header("Operation Comparison")]
|
||||
public int doorwayPriority;
|
||||
public int doorwayPriorityB;
|
||||
public Operation operation = Operation.Equal;
|
||||
|
||||
[Header("Targets")]
|
||||
@ -20,7 +22,7 @@ namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||
public override void Cleanup(DoorwayCleanup parent) {
|
||||
var doorway = parent.doorway;
|
||||
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) {
|
||||
foreach(var t in targets) t.SetActive(false);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||
[Obsolete("Please use DoorwayScriptingParent")]
|
||||
public abstract class DoorwayCleanupScript : MonoBehaviour {
|
||||
|
||||
public abstract void Cleanup(DoorwayCleanup parent);
|
||||
|
@ -4,13 +4,44 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||
[Obsolete("Please use DoorwayScriptingParent")]
|
||||
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){
|
||||
case Operation.Equal:
|
||||
return EqualOperation;
|
||||
@ -20,24 +51,48 @@ namespace DunGenPlus.Components.DoorwayCleanupScripting {
|
||||
return LessThanOperation;
|
||||
case Operation.GreaterThan:
|
||||
return GreaterThanOperation;
|
||||
case Operation.LessThanEq:
|
||||
return LessThanEqualOperation;
|
||||
case Operation.GreaterThanEq:
|
||||
return GreaterThanEqualOperation;
|
||||
case Operation.Between:
|
||||
return BetweenOperation;
|
||||
case Operation.BetweenEq:
|
||||
return BetweenEqualOperation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool EqualOperation(Doorway other, int doorwayPriority){
|
||||
return other.DoorPrefabPriority == doorwayPriority;
|
||||
public bool EqualOperation(Doorway other, Arguments arguments){
|
||||
return other.DoorPrefabPriority == arguments.parameterA;
|
||||
}
|
||||
|
||||
public bool NotEqualOperation(Doorway other, int doorwayPriority){
|
||||
return other.DoorPrefabPriority != doorwayPriority;
|
||||
public bool NotEqualOperation(Doorway other, Arguments arguments){
|
||||
return other.DoorPrefabPriority != arguments.parameterA;
|
||||
}
|
||||
|
||||
public bool LessThanOperation(Doorway other, int doorwayPriority){
|
||||
return other.DoorPrefabPriority < doorwayPriority;
|
||||
public bool LessThanOperation(Doorway other, Arguments arguments){
|
||||
return other.DoorPrefabPriority < arguments.parameterA;
|
||||
}
|
||||
|
||||
public bool GreaterThanOperation(Doorway other, int doorwayPriority){
|
||||
return other.DoorPrefabPriority > doorwayPriority;
|
||||
public bool GreaterThanOperation(Doorway other, Arguments arguments){
|
||||
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 branchLoopBoostParentGameobject;
|
||||
private GameObject maxShadowsParentGameobject;
|
||||
public bool eventCallbackValue = true;
|
||||
|
||||
public override void AwakeCall() {
|
||||
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 EventCallbackScenarioTooltip = "Sets the EventCallbackScenario.IsDevDebug value";
|
||||
|
||||
public void SetupPanel() {
|
||||
selectedExtenderer = API.GetDunGenExtender(selectedDungeonFlow);
|
||||
@ -77,6 +79,7 @@ namespace DunGenPlus.DevTools.Panels {
|
||||
var parentTransform = selectedListGameObject.transform;
|
||||
var properties = selectedExtenderer.Properties;
|
||||
manager.CreateBoolInputField(parentTransform, ("Activate DunGenPlus", ActivateDunGenPlusTooltip), selectedExtenderer.Active, SetActivateDunGenPlus);
|
||||
manager.CreateBoolInputField(parentTransform, ("EventCallbackScenario state", EventCallbackScenarioTooltip), eventCallbackValue, SetDebugCallbackState);
|
||||
manager.CreateSpaceUIField(parentTransform);
|
||||
|
||||
var mainPathTransform = manager.CreateVerticalLayoutUIField(parentTransform);
|
||||
@ -168,6 +171,10 @@ namespace DunGenPlus.DevTools.Panels {
|
||||
selectedExtenderer.Active = state;
|
||||
}
|
||||
|
||||
public void SetDebugCallbackState(bool state){
|
||||
eventCallbackValue = state;
|
||||
}
|
||||
|
||||
public void SetMainPathCount(int value) {
|
||||
selectedExtenderer.Properties.MainPathProperties.MainPathCount = value;
|
||||
mainPathParentGameobject.SetActive(value > 1);
|
||||
|
@ -56,6 +56,9 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\Libraries\MonoMod.Utils.dll</HintPath>
|
||||
</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.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
@ -157,7 +160,24 @@
|
||||
<Compile Include="Components\MainRoomDoorwayGroups.cs" />
|
||||
<Compile Include="Components\Props\SpawnSyncedObjectCycle.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="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\DunGenPlusGeneratorDebug.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 DunGenPlus.DevTools;
|
||||
using DunGenPlus.Patches;
|
||||
using DunGenPlus.DevTools.Panels;
|
||||
|
||||
[assembly: SecurityPermission( SecurityAction.RequestMinimum, SkipVerification = true )]
|
||||
namespace DunGenPlus.Generation {
|
||||
@ -36,7 +37,7 @@ namespace DunGenPlus.Generation {
|
||||
ActiveAlternative = true;
|
||||
|
||||
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);
|
||||
props.NormalNodeArchetypesProperties.SetupProperties(generator);
|
||||
Properties = props;
|
||||
|
@ -1,36 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DunGen;
|
||||
using DunGen.Adapters;
|
||||
using DunGenPlus.Components;
|
||||
using DunGenPlus.Components.Scripting;
|
||||
using DunGenPlus.Generation;
|
||||
using DunGenPlus.Utils;
|
||||
using static DunGenPlus.Managers.DoorwayManager;
|
||||
|
||||
namespace DunGenPlus.Managers {
|
||||
public static class DoorwayManager {
|
||||
|
||||
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(){
|
||||
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){
|
||||
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) {
|
||||
foreach(var d in doorwayCleanupList){
|
||||
d.SetBlockers(false);
|
||||
d.Cleanup();
|
||||
|
||||
//foreach(var d in doorwayCleanupList){
|
||||
// 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)
|
||||
if (doorwayCleanupList.Count == 0) return;
|
||||
if (!anyFunctionCalled) return;
|
||||
|
||||
try{
|
||||
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.Graph;
|
||||
using DunGenPlus.Collections;
|
||||
using DunGenPlus.Components.Scripting;
|
||||
using DunGenPlus.Generation;
|
||||
using DunGenPlus.Managers;
|
||||
using DunGenPlus.Patches;
|
||||
@ -26,7 +27,7 @@ namespace DunGenPlus {
|
||||
|
||||
internal const string modGUID = "dev.ladyalice.dungenplus";
|
||||
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);
|
||||
|
||||
@ -62,7 +63,7 @@ namespace DunGenPlus {
|
||||
|
||||
Assets.LoadAssets();
|
||||
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.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
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>
|
||||
<Compile Include="DunGenExtenderPropertyDrawer.cs" />
|
||||
<Compile Include="NamedGameObjectReferencePropertyDrawer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="PropertyDrawerUtility.cs" />
|
||||
<Compile Include="PropertyOverridePropertyDrawer.cs" />
|
||||
<Compile Include="ReadOnlyPropertyDrawer.cs" />
|
||||
<Compile Include="ScriptActionPropertyDrawer.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<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