Angel \”Java\” Lopez on Blog

October 1, 2010

Writing an Interpreter in .NET (Part 7)

Filed under: .NET, Programming Languages, Test-Driven Development — ajlopez @ 8:12 pm

Back to topic! a new installment in this post series. I was writing an interpreter, using TDD approach and .NET. I have a parser, a lexer, some expressions and only one command. It’s time to add a new command to my interpreter. The new command is the IfCommand:

You can download the source code from Interpreter07.zip.

The IfCommand class implements the interface ICommand. It was built using TDD. My first tests:

        [TestMethod]
        public void CreateIfCommand()
        {
            IExpression condition = new ConstantExpression(0);
            ICommand thenCommand = new SetCommand("a", new ConstantExpression(1));
            ICommand elseCommand = new SetCommand("b", new ConstantExpression(2));
            IfCommand command = new IfCommand(condition, thenCommand, elseCommand);
            Assert.AreEqual(condition, command.Condition);
            Assert.AreEqual(thenCommand, command.ThenCommand);
            Assert.AreEqual(elseCommand, command.ElseCommand);
        }
        [TestMethod]
        public void EvaluateIfCommandWithZeroAsCondition()
        {
            IExpression condition = new ConstantExpression(0);
            ICommand thenCommand = new SetCommand("a", new ConstantExpression(1));
            ICommand elseCommand = new SetCommand("b", new ConstantExpression(2));
            IfCommand command = new IfCommand(condition, thenCommand, elseCommand);
            BindingEnvironment environment = new BindingEnvironment();
            command.Execute(environment);
            Assert.IsNull(environment.GetValue("a"));
            Assert.AreEqual(2, environment.GetValue("b"));
        }

The implementation of IfCommand:

    public class IfCommand : ICommand
    {
        private IExpression condition;
        private ICommand thenCommand;
        private ICommand elseCommand;
        public IfCommand(IExpression condition, ICommand thenCommand)
            : this(condition, thenCommand, null)
        {
        }
        public IfCommand(IExpression condition, ICommand thenCommand, ICommand elseCommand)
        {
            this.condition = condition;
            this.thenCommand = thenCommand;
            this.elseCommand = elseCommand;
        }
        public IExpression Condition { get { return this.condition; } }
        public ICommand ThenCommand { get { return this.thenCommand; } }
        public ICommand ElseCommand { get { return this.elseCommand; } }
        public void Execute(BindingEnvironment environment)
        {
            object result = this.condition.Evaluate(environment);
            bool cond = !IsFalse(result);
            if (cond)
                this.thenCommand.Execute(environment);
            else if (this.elseCommand != null)
                this.elseCommand.Execute(environment);
        }
        private static bool IsFalse(object obj)
        {
            if (obj == null)
                return true;
            if (obj is bool)
                return !(bool)obj;
            if (obj is int)
                return (int)obj == 0;
            if (obj is string)
                return string.IsNullOrEmpty((string)obj);
            if (obj is long)
                return (long)obj == 0;
            if (obj is short)
                return (short)obj == 0;
            if (obj is double)
                return (double)obj == 0;
            if (obj is float)
                return (float)obj == 0;
            return false;
        }
    }

IfCommand evaluates an expression, that returns an object. This object could not be a boolean. I choose to evaluate null, 0, empty string as false. The only place I need to evaluate an object as true or false is in the IfCommand.Execute method. So, the boolean evaluate logic is in a private method. I plan to refactor it, moving to another class, when I need to use it from WhileCommand and others expressions.

After writing IfCommand, I needed to parse if commands. I had no .ParseCommand() method in Parser class. My first tests (more test in the source code):


Then, I implemented new methods in Parser:

Parser.ParseCommand() has a naive implementation. Only two kind of commands are supported: if commands, and set variable commands:

        public ICommand ParseCommand()
        {
            Token token = this.NextToken();
            if (token == null)
                return null;
            if (token.TokenType == TokenType.Name && token.Value.Equals("if"))
                return ParseIfCommand();
            this.PushToken(token);
            return ParseSetCommand();
        }
        private ICommand ParseSetCommand()
        {
            string name = this.ParseName();
            this.ParseToken(TokenType.Operator, "=");
            IExpression expr = this.ParseExpression();
            this.ParseToken(TokenType.Separator, ";");
            return new SetCommand(name, expr);
        }
        private ICommand ParseIfCommand()
        {
            IExpression condition;
            ICommand thencmd;
            ICommand elsecmd;
            this.ParseToken(TokenType.Separator, "(");
            condition = this.ParseExpression();
            this.ParseToken(TokenType.Separator, ")");
            thencmd = this.ParseCommand();
            Token token = this.NextToken();
            if (token != null && token.TokenType == TokenType.Name && token.Value.Equals("else")) 
            {
                elsecmd = this.ParseCommand();
                return new IfCommand(condition, thencmd, elsecmd);
            }
            if (token != null)
                this.PushToken(token);
            return new IfCommand(condition, thencmd);
        }

As usual, all tests are green:

Good code coverage:

Next steps: add more commands (while, for, etc…), function declaration, real numbers, etc.

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

The Shocking Blue Green Theme Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 56 other followers