tags:

views:

124

answers:

3

I'm building a simple assembly compiler for my own custom flavor of assembly, and i have something like this as the actual code that does the compiling:

    foreach (KeyValuePair<short, string> kvp in newCommandSet)
    {
        string fullCommandString = kvp.Value;

        string instruction = fullCommandString.Split(new char[] { Convert.ToChar(" ") })[0];
        string[] parameters = fullCommandString.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
        // this is to remove the instruction part from the first parameter. Gonna have to ensure a well formed command at some point...
        parameters[0] = parameters[0].Substring(instruction.Length + 1);
        Command currentCommand = new Command();
        switch (instruction)
        {
            case "load":
                short value = Convert.ToInt16(instruction[0]);
                byte register = Convert.ToByte(parameters[1]);
                currentCommand = CommandFactory.CreateLoadCommand(register, value);
                break;
            case "input":
                byte channel = Convert.ToByte(parameters[0]);
                register = Convert.ToByte(parameters[1]);
                currentCommand = CommandFactory.CreateInputCommand(register, channel);
                break;
            case "output":
                channel = Convert.ToByte(parameters[0]);
                register = Convert.ToByte(parameters[1]);
                currentCommand = CommandFactory.CreateInputCommand(register, channel);
                break;
            ...
        }
        ...
    }

It feels like i'm breaking about half a dozen design rules here (reusing variables and expecting well-formed input are the only ones i can spot but i bet there's more), but have no clue how to build it any better. Ideas?

+4  A: 

You might consider throwing a couple things in like a tokenizer that returns your program as a string of tokens (your splitter kind of does this). Then pass that off to a parser to create a parse tree and symbol table. Why? because without knowing your flavor of assembly, at some point you are going to want to jump to a label (subroutine) I would assume. or you will want your jump instruction to head back to the start of a loop, etc...

If you have your parse tree and symbol table set up, you will have all the addresses right there for easy insertion into your output file. It's been a long long time since I wrote a compiler, so please forgive any deviations in my little example...

Zak
I actually came up with a clever little bit of code just before that, which generates a "jump map" so i can emit jump addresses into my bytecode instead of the labels that are currently in place.
RCIX
Though if you could point me to some libraries/resources for those things (tokenizer, parser), that would be nice...
RCIX
sorry, not really familiar with .net . Since you are writing a pretty raw program here though, there is no reason you can't make your own. Something like this:Pull the loop out into a "Tokenizer" classPull all the inner code out of your case statements, and return a constant as the "token" of course, explicitly define your constants.Parser:Take the tokens in 1 at a time. Keep track of your various states (started loop, in loop, starting subroutine, in subroutine) you should have a simple grammar, even for your assembly language, so just write out each case and group inside your parser.
Zak
I'll take a shot. Thanks for the help!
RCIX
+5  A: 

Consider pushing the logic for interpreting the parameters into the CommandFactory. The switch statement would look as follows:

switch(instruction)
{
    case "load":
        currentCommand = CommandFactory.CreateLoadCommand(parameters);
        break;
    case "input":
        currentCommand = CommandFactory.CreateInputCommand(parameters);
        break;
    case "output":
        currentCommand = CommandFactory.CreateOutputCommand(parameters);
        break;
}
Igor ostrovsky
SO you're saying also offer an overload of CommandFactory functions that can parse string parameters? i like it....
RCIX
A: 

Move the instruction info to a class / property bag. Create some utility methods for conversions to make your life easier. Then use a string -> delegate dictionary to map the instruction name to creation of a command. This is just a start, you can refactor this to be a great deal simpler.

Something along these lines, perhaps:

public class InstructionData
{
    public InstructionData(string fullCommandString)
    {
        string[] commandParts = fullCommandString.Split(new char[] {' ', ','}, StringSplitOptions.RemoveEmptyEntries);
        this.InstructionName = commandParts[0];
        this.parameters = commandParts.Skip(1).ToArray();
    }

    public string InstructionName { get; private set; }
    public short InstructionInt { get { return Convert.ToInt16(InstructionName[0]); } }
    private string[] parameters;
    public string GetParameter(int paramNum)
    {
        return parameters[paramNum];
    }
    public byte GetParameterAsByte(int paramNum)
    {
        return Convert.ToByte(parameters[paramNum]);
    }
}


public class SomeClass
{
    // ...
    private Dictionary<string, Func<InstructionData, Command>> commandTranslator = new Dictionary<string, Func<InstructionData, Command>>();

    private static void InitializeCommandTranslator()
    {
        commandTranslator["load"] = ins => CommandFactory.CreateLoadCommand(ins);
        commandTranslator["input"] = ins => CommandFactory.CreateInputCommand(ins);
        commandTranslator["output"] = ins => CommandFactory.CreateOutputCommand(ins);

    }

    public void SomeMethod()
    {
     // ...
        foreach (KeyValuePair<short, string> kvp in newCommandSet)
        {
         InstructionData currentInstruction = new InstructionData(kvp.Value);

            if(commandTranslator.ContainsKey(currentInstruction.InstructionName))
            {
                currentCommand = commandTranslator[currentInstruction.InstructionName](currentInstruction);
            }
        }
    }

    // ...
}
Wedge