【问题标题】:Writing an interpreter in C#: Best way to implement instructions?用 C# 编写解释器:实现指令的最佳方式?
【发布时间】:2012-03-29 11:26:27
【问题描述】:

我正在使用 C# 编写一个 PLC 语言解释器。该 PLC 语言包含 20 多种数据类型和 25 条左右的指令。当我开始生成代码时,我会平衡两种不同的指令编写方式:

1) 每一种指令都在一个类中表示,其中包含一个大的switch,以便选择数据类型。示例:

public class ADD : Instruction
{
    private string type;

    public ADD(string type)
    {
        this.type = type;
    }

    public bool Exec(Context c)
    {
        switch (type)
        {
            case "INT":
                short valor2 = c.PopINT();
                short valor = c.PopINT();
                short result = (short)(valor + valor2);
                c.PushINT(result);
                break;
            case "DINT":
                int valor4 = c.PopDINT();
                int valor3 = c.PopDINT();
                int result2 = (int)(valor4 + valor3);
                c.PushDINT(result2);
                break;
            case "BOOL":
                // Implement BOOL
                break;
            // Implement other types...
            default:
                break;
        }

        c.IP++;
        return false; ;
    }

}

2) 每个类代表具有单一数据类型的单一指令。这样可以避免大的switch。示例:

public class ADDi : Instruction
{
    public bool Exec(Context c)
    {
        short valor = c.PopINT();
        short valor2 = c.PopINT();
        short result = (short)(valor + valor2);
        c.PushINT(result);
        c.IP++;
        return false;
    }
}

我正在使用 COMMAND 设计模式 (Exec()) 来编写指令。我认为第二个选择更好,因为它避免了大开关,但该选择涉及编写超过 400 条指令。

请始终牢记,在这种情况下,执行性能比翻译性能更重要。

所以,我的确切问题如下:有没有其他方法来分解指令和数据类型?我希望在不影响性能的情况下编写更少的指令。

编辑:

这张图显示了我的类型层次结构:

这是 INT 类实现:

public class INT : ANY_INT
{

    public override string DefaultInitValue()
    {
        return "0";
    }

    public override int GetBytes()
    {
        return 2;
    }

    public override string GetLastType()
    {
        return this.ToString();
    }

    public override string ToString()
    {
        return "INT";
    }

}

有些类更复杂(结构、数组等)。

Push 和 Pop 操作定义如下:

public void PushINT(short value)
{
    //SP -> Stack Pointer
    resMem.WriteINT(SP, value);
    SP += 2;
}

public short PopINT()
{
    SP -= 2;
    short value = resMem.ReadINT(SP);
    return value;
}

最后是在内存中读写的操作。

public void WriteINT(int index, short entero)
{
    SetCapacity(index + 2); // Memory grows up dinamically
    memory[index] = (sbyte)((ushort)entero >> 8 & 0x00FF);
    memory[index + 1] = (sbyte)((ushort)entero >> 0 & 0x00FF);
}

public short ReadINT(int index)
{            
    return (short)(((short)(memory[index]) << 8 & 0xFF00) |
       ((short)(memory[index + 1]) & 0x00FF));
}

我希望这些信息对您有所帮助。谢谢你。

【问题讨论】:

  • 我会为语言中的每个非终结符号使用一个类,然后分而治之,以寻求解决方案。
  • 你完成这个项目了吗,你的代码是开源的吗?

标签: c# interpreter plc instruction-set


【解决方案1】:

如果您可以更改 Context 的实现以支持泛型类型(例如,Pop&lt;int&gt; 而不是 PopINT()),您可以使用委托来简化实现。

加法:

var addInt = new MathInstruction<int>((a, b) => a + b));
var addDouble = new MathInstruction<double>((a, b) => a + b));
var addDecimal = new MathInstruction<decimal>((a, b) => a + b));

减法:

var subtractInt = new MathInstruction<int>((a, b) => a - b));
var subtractDouble = new MathInstruction<double>((a, b) => a - b));
var subtractDecimal = new MathInstruction<decimal>((a, b) => a - b));

部门:

var divideIntAsDouble = new MathInstruction<int, double>((a, b) => a / b));
var divideDouble = new MathInstruction<double>((a, b) => a / b));
var divideDecimal = new MathInstruction<decimal>((a, b) => a / b));

以及类型之间的转换:

var addIntAndDouble = new MathInstruction<int, double, double>((a, b) => a + b));

它会这样实现:

class MathInstruction<TA, TB, TResult> : Instruction
{
    private Func<TA, TB, TResult> callback;

    public MathInstruction(Func<TA, TB, TResult> callback) 
    {
        this.callback = callback;
    }

    public bool Exec(Context c)
    {
        var a = c.Pop<TA>();
        var b = c.Pop<TB>();
        var result = callback(a, b);
        c.Push<TResult>(result);
        return false;
    }
}

// Convenience
class MathInstruction<T, TResult> : MathInstruction<T, T, TResult>
class MathInstruction<T> : MathInstruction<T, T, T>

我想你的上下文只是有一个Stack&lt;object&gt;PopINTPopBOOL 等。只需弹出参数并强制转换。在这种情况下,您可能只需使用:

public T Pop<T>()
{
    var o = stack.Pop();
    return Convert.ChangeType(o, typeof(T));
} 

public void Push<T>(T item)
{
    stack.Push(item);
} 

请注意,这也可以处理您的逻辑运算符 - 例如:

var logicalAnd = new MathInstruction<bool>((a, b) => a && b);
var logicalOr = new MathInstruction<bool>((a, b) => a || b);

【讨论】:

  • 非常感谢您的详细解释。感谢您的快速答复。我一直在等待回复你,因为我从未使用过委托,而且我正在获取一些关于它们的信息。
  • 关于您的回答,我不能使用通用数据类型。我编写了自己的类型层次结构和自己的堆栈表示。那么,有什么方法可以使您以前的代码适应用户创建的数据类型?再次感谢您。
  • 你能发布一个你的类型层次结构和你的 Pop 和 Push 实现的例子吗?
  • 是的,没问题。我将编辑我的帖子以添加该信息。谢谢。
  • 完成。我受够了。如果您需要更多信息,请告诉我 ;)
【解决方案2】:

你可以使用继承吗?我会看到有关数据类型的继承的巧妙组合,然后是将执行委托给适当对象的策略模式。

但是我们真的需要看一个类图来帮助你。

记住要编程到一个接口,而不是一个类型,而且,组合比继承更强大。我希望这可以帮助你。

【讨论】:

  • 感谢您的回答。我有自己的类型层次结构。因此,我必须在指令类中使用组合(将“字符串类型”交换为“OwnType 类型”),然后在我的数据类型类中编写“具体策略方法”,最后,编写方法来选择指令中的策略。对吗?
猜你喜欢
  • 1970-01-01
  • 2015-11-21
  • 1970-01-01
  • 1970-01-01
  • 2016-06-14
  • 1970-01-01
  • 2016-07-17
  • 1970-01-01
  • 2012-03-14
相关资源
最近更新 更多