276 lines
10 KiB
C#
276 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.Serialization;
|
|
|
|
namespace Cryville.Common.Pdt {
|
|
/// <summary>
|
|
/// Base of evaluator for PDT expressions.
|
|
/// </summary>
|
|
public abstract class PdtEvaluatorBase {
|
|
private struct StackFrame {
|
|
public int Offset;
|
|
public int Length;
|
|
public int Type;
|
|
}
|
|
int _framecount = 0;
|
|
int _goffset = 0;
|
|
readonly StackFrame[] _stack = new StackFrame[256];
|
|
readonly byte[] _mem = new byte[0x100000];
|
|
bool _revokepttconst;
|
|
/// <summary>
|
|
/// Evaluates an expression and passes the result to a target operator.
|
|
/// </summary>
|
|
/// <param name="target">The target operator.</param>
|
|
/// <param name="exp">The expression to evaluate.</param>
|
|
/// <returns>Whether the evaluaton succeeded.</returns>
|
|
public bool Evaluate(PdtOperator target, PdtExpression exp) {
|
|
var prevFrameCount = _framecount;
|
|
try {
|
|
_revokepttconst = false;
|
|
for (var ip = exp.Instructions.First; ip != null; ip = ip.Next)
|
|
ip.Value.Execute(this, ref ip);
|
|
if (exp.IsPotentialConstant) {
|
|
exp.IsConstant = exp.IsPotentialConstant = !_revokepttconst;
|
|
}
|
|
var ret = Operate(target, _framecount - prevFrameCount, true);
|
|
return ret;
|
|
}
|
|
catch (Exception ex) {
|
|
throw new EvaluationFailureException(exp, ex);
|
|
}
|
|
finally {
|
|
for (var i = prevFrameCount; i < _framecount; i++) DiscardStack();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Optimizes an expression by merging its instructions.
|
|
/// </summary>
|
|
/// <param name="exp">The expression to optimize.</param>
|
|
public void Optimize(PdtExpression exp) {
|
|
_framecount = 0;
|
|
_goffset = 0;
|
|
List<PdtInstruction.Collapse> ct;
|
|
var cols = new Dictionary<LinkedListNode<PdtInstruction>, List<PdtInstruction.Collapse>>();
|
|
var il = exp.Instructions;
|
|
var ip = il.First;
|
|
while (ip != null) {
|
|
bool nextFlag = false;
|
|
var i = ip.Value;
|
|
if (i is PdtInstruction.Operate iop) {
|
|
int fc0 = _framecount;
|
|
int fc1 = iop.Signature.ParamCount;
|
|
try { i.Execute(this, ref ip); } catch (Exception) { }
|
|
if (fc0 - _framecount == fc1) {
|
|
unsafe {
|
|
fixed (StackFrame* frame = &_stack[_framecount++]) {
|
|
frame->Type = PdtInternalType.Error;
|
|
frame->Offset = -1;
|
|
frame->Length = 0;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
var frame = _stack[_framecount - 1];
|
|
if (frame.Type != PdtInternalType.Error) {
|
|
ReplaceIP(il, ref ip, new PdtInstruction.PushConstant(frame.Type, _mem, frame.Offset, frame.Length), cols);
|
|
for (var j = 0; j < fc1; j++) il.Remove(ip.Previous);
|
|
}
|
|
}
|
|
}
|
|
else if (i is PdtInstruction.Collapse t) {
|
|
try {
|
|
var pins = ip;
|
|
i.Execute(this, ref ip);
|
|
if (_stack[_framecount - 1].Type == PdtInternalType.Error) {
|
|
throw new EvaluationFailureException();
|
|
}
|
|
if (ip == pins) {
|
|
ip = ip.Next;
|
|
il.Remove(ip.Previous);
|
|
il.Remove(ip.Previous);
|
|
ip = ip.Previous;
|
|
if (ip == null) {
|
|
ip = il.First;
|
|
nextFlag = true;
|
|
}
|
|
}
|
|
else {
|
|
ip = pins.Previous;
|
|
while (ip.Next != t.Target) il.Remove(ip.Next);
|
|
il.Remove(ip.Next);
|
|
if (cols.TryGetValue(t.Target, out ct)) {
|
|
foreach (var u in ct) u.Target = ip;
|
|
cols.Remove(t.Target);
|
|
cols.Add(ip, ct);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception) {
|
|
if (cols.TryGetValue(t.Target, out ct)) ct.Add(t);
|
|
else cols.Add(t.Target, new List<PdtInstruction.Collapse> { t });
|
|
}
|
|
}
|
|
else if (i is PdtInstruction.PushVariable) {
|
|
i.Execute(this, ref ip);
|
|
var frame = _stack[_framecount - 1];
|
|
if (frame.Type != PdtInternalType.Undefined && frame.Type != PdtInternalType.Error) {
|
|
ReplaceIP(il, ref ip, new PdtInstruction.PushConstant(frame.Type, _mem, frame.Offset, frame.Length), cols);
|
|
}
|
|
}
|
|
else i.Execute(this, ref ip);
|
|
if (ip != null && cols.TryGetValue(ip, out ct)) {
|
|
unsafe {
|
|
fixed (StackFrame* frame = &_stack[_framecount - 1]) {
|
|
frame->Type = PdtInternalType.Error;
|
|
frame->Offset = -1;
|
|
frame->Length = 0;
|
|
}
|
|
}
|
|
}
|
|
if (!nextFlag) ip = ip.Next;
|
|
}
|
|
exp.IsConstant = true;
|
|
exp.IsPotentialConstant = true;
|
|
for (var ins = il.First; ins != null; ins = ins.Next) {
|
|
if (ins.Value is not PdtInstruction.PushConstant) {
|
|
exp.IsConstant = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
void ReplaceIP(LinkedList<PdtInstruction> il, ref LinkedListNode<PdtInstruction> ip, PdtInstruction ins, Dictionary<LinkedListNode<PdtInstruction>, List<PdtInstruction.Collapse>> cols) {
|
|
if (cols.TryGetValue(ip, out List<PdtInstruction.Collapse> cins)) cols.Remove(ip);
|
|
ip = il.AddAfter(ip, ins);
|
|
il.Remove(ip.Previous);
|
|
if (cins != null) cols.Add(ip, cins);
|
|
}
|
|
/// <summary>
|
|
/// Revokes the potential constant mark of the current expression.
|
|
/// </summary>
|
|
protected void RevokePotentialConstant() {
|
|
_revokepttconst = true;
|
|
}
|
|
internal unsafe void PushConstant(int type, byte[] value) {
|
|
fixed (StackFrame* frame = &_stack[_framecount++]) {
|
|
frame->Type = type;
|
|
frame->Offset = _goffset;
|
|
frame->Length = value.Length;
|
|
Array.Copy(value, 0, _mem, _goffset, value.Length);
|
|
_goffset += value.Length;
|
|
}
|
|
}
|
|
internal unsafe void PushVariable(int name, bool forced) {
|
|
fixed (StackFrame* frame = &_stack[_framecount++]) {
|
|
GetVariable(name, forced, out frame->Type, out byte[] value);
|
|
frame->Offset = _goffset;
|
|
frame->Length = value.Length;
|
|
Array.Copy(value, 0, _mem, _goffset, value.Length);
|
|
_goffset += value.Length;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Gets a variable of the specified name.
|
|
/// </summary>
|
|
/// <param name="name">The name of the variable.</param>
|
|
/// <param name="forced">Whether to produce an error stack instead of an identifier stack if the variable is not found.</param>
|
|
/// <param name="type">The type of the variable.</param>
|
|
/// <param name="value">The value of the variable.</param>
|
|
protected abstract void GetVariable(int name, bool forced, out int type, out byte[] value);
|
|
internal void Operate(PdtOperatorSignature sig) {
|
|
PdtOperator op;
|
|
try { op = GetOperator(sig); }
|
|
catch (Exception ex) {
|
|
for (int i = 0; i < sig.ParamCount; i++)
|
|
DiscardStack();
|
|
throw new EvaluationFailureException(string.Format("Failed to get operator {0}", sig), ex);
|
|
}
|
|
Operate(op, sig.ParamCount);
|
|
}
|
|
/// <summary>
|
|
/// Gets an operator of the specified name and the suggested parameter count.
|
|
/// </summary>
|
|
/// <param name="name">The name of the operator.</param>
|
|
/// <param name="pc">Suggested parameter count.</param>
|
|
/// <returns>An operator of the specific name.</returns>
|
|
/// <remarks>The parameter count of the returned operator does not necessarily equal to <paramref name="pc" />.</remarks>
|
|
protected abstract PdtOperator GetOperator(PdtOperatorSignature sig);
|
|
unsafe bool Operate(PdtOperator op, int pc, bool noset = false) {
|
|
fixed (byte* pmem = _mem) {
|
|
op.Begin(this, pc);
|
|
for (int i = 0; i < pc; i++) {
|
|
var frame = _stack[--_framecount];
|
|
if (frame.Type == PdtInternalType.Error) {
|
|
_framecount -= pc - i - 1;
|
|
_stack[_framecount++] = new StackFrame { Type = PdtInternalType.Error, Offset = -1, Length = 0 };
|
|
return false;
|
|
}
|
|
op.LoadOperand(new PdtVariableMemory(frame.Type, pmem + frame.Offset, frame.Length));
|
|
_goffset -= frame.Length;
|
|
}
|
|
op.Call(pmem + _goffset, noset);
|
|
return true;
|
|
}
|
|
}
|
|
internal unsafe void Collapse(int name, ref LinkedListNode<PdtInstruction> self, LinkedListNode<PdtInstruction> target) {
|
|
fixed (byte* pmem = _mem) {
|
|
var frame = _stack[--_framecount];
|
|
_goffset -= frame.Length;
|
|
if (frame.Type == PdtInternalType.Error) {
|
|
_stack[_framecount++] = new StackFrame { Type = PdtInternalType.Error, Offset = -1, Length = 0 };
|
|
self = target;
|
|
return;
|
|
}
|
|
if (Collapse(name, new PdtVariableMemory(frame.Type, pmem + frame.Offset, frame.Length))) {
|
|
_framecount++;
|
|
_goffset += frame.Length;
|
|
self = target;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Gets whether to jump to the target of a collapse instruction.
|
|
/// </summary>
|
|
/// <param name="name">The name of the collapse operator.</param>
|
|
/// <param name="param">The top frame in the stack as the parameter.</param>
|
|
/// <returns>Whether to jump to the target of the collapse instruction.</returns>
|
|
protected abstract bool Collapse(int name, PdtVariableMemory param);
|
|
internal unsafe PdtVariableMemory StackAlloc(int type, byte* ptr, int len) {
|
|
fixed (StackFrame* frame = &_stack[_framecount++]) {
|
|
frame->Type = type;
|
|
frame->Offset = _goffset;
|
|
frame->Length = len;
|
|
_goffset += len;
|
|
return new PdtVariableMemory(type, ptr, len);
|
|
}
|
|
}
|
|
internal void DiscardStack() {
|
|
_goffset -= _stack[--_framecount].Length;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The exception that is thrown when the evaluation of a <see cref="PdtExpression" /> fails.
|
|
/// </summary>
|
|
public class EvaluationFailureException : Exception {
|
|
/// <inheritdoc />
|
|
public EvaluationFailureException() : base("Evaluation failed") { }
|
|
/// <inheritdoc />
|
|
public EvaluationFailureException(string message) : base(message) { }
|
|
/// <inheritdoc />
|
|
public EvaluationFailureException(string message, Exception innerException) : base(message, innerException) { }
|
|
/// <inheritdoc />
|
|
protected EvaluationFailureException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
|
/// <summary>
|
|
/// Creates an instance of the <see cref="EvaluationFailureException" /> class with the failing expression.
|
|
/// </summary>
|
|
/// <param name="exp">The failing expression.</param>
|
|
public EvaluationFailureException(PdtExpression exp) : base("Evaluation failed for the expression: " + exp.ToString()) { }
|
|
/// <summary>
|
|
/// Creates an instance of the <see cref="EvaluationFailureException" /> class with the failing expression and the inner exception.
|
|
/// </summary>
|
|
/// <param name="exp">The failing expression.</param>
|
|
/// <param name="innerException">The inner exception.</param>
|
|
public EvaluationFailureException(PdtExpression exp, Exception innerException) : base("Evaluation failed for the expression: " + exp.ToString(), innerException) { }
|
|
}
|
|
}
|