Angel \”Java\” Lopez on Blog

January 27, 2011

Writing an Interpreter in .NET (Part 9)

Filed under: .NET, Programming Languages, Test-Driven Development — ajlopez @ 11:21 am

Previous Post
First post of the series

This time, I want to add a missing part: composite command. The interpreter needs a way to execute a list of commands, at any point: at if/then, if/else, while, etc… First, I wrote a test (this is not the initial version; it’s the current one):

        [TestMethod]
        public void CreateAndEvaluateCompositeCommand()
        {
            BindingEnvironment environment = new BindingEnvironment();
            environment.SetValue("a", 0);
            ICommand add1 = this.MakeAddCommand("a", "a", 1);
            ICommand add2 = this.MakeAddCommand("a", "a", 2);
            CompositeCommand cmd = new CompositeCommand(new ICommand[] { add1, add2 });
            Assert.IsNotNull(cmd.Commands);
            Assert.AreEqual(2, cmd.Commands.Count);
            Assert.AreEqual(add1, cmd.Commands.First());
            Assert.AreEqual(add2, cmd.Commands.Skip(1).First());
            cmd.Execute(environment);
            Assert.AreEqual(3, environment.GetValue("a"));
        }
        private ICommand MakeAddCommand(string target, string source, int number)
        {
            IExpression add = new BinaryArithmeticExpression(new VariableExpression(source), new ConstantExpression(number), ArithmeticOperator.Add);
            ICommand set = new SetCommand(target, add);
            return set;
        }

Then, I added a new ICommand, the CompositeCommand, refined until it pass the test:

I need to support the parse of a composite command. I decided to use the C#/Javascript syntax: the composite command should be delimited by { and }. I realized that I had no support of such characters in Lexer, so I wrote this test:

        [TestMethod]
        public void ProcessCurlyBrackets()
        {
            Lexer lexer = new Lexer("{}");
            Token token = lexer.NextToken();
            Assert.IsNotNull(token);
            Assert.AreEqual(TokenType.Separator, token.TokenType);
            Assert.AreEqual("{", token.Value);
            token = lexer.NextToken();
            Assert.IsNotNull(token);
            Assert.AreEqual(TokenType.Separator, token.TokenType);
            Assert.AreEqual("}", token.Value);
            Assert.IsNull(lexer.NextToken());
        }

Then, I modified the Lexer source, simply adding the new separators:

private static string[] separators = new string[] { ";", "(", ")", "{", "}" };

Next step: to add support of composite commands in Parser. As usual, the test to exercise the Parser:

        [TestMethod]
        public void ParseCompositeCommand()
        {
            Parser parser = new Parser("{ a = a+1; a = a+2; }");
            ICommand command = parser.ParseCommand();
            Assert.IsNotNull(command);
            Assert.IsInstanceOfType(command, typeof(CompositeCommand));
            CompositeCommand ccmd = (CompositeCommand)command;
            Assert.IsNotNull(ccmd.Commands);
            Assert.AreEqual(2, ccmd.Commands.Count);
            Assert.IsInstanceOfType(ccmd.Commands.First(), typeof(SetCommand));
            Assert.IsInstanceOfType(ccmd.Commands.Skip(1).First(), typeof(SetCommand));
        }

Then I added the code in Parser.ParseCommand (partial listing):

    if (token.TokenType == TokenType.Separator && token.Value.Equals("{"))
        return this.ParseCompositeCommand();

The new method:

    private ICommand ParseCompositeCommand()
    {
        IList<ICommand> commands = new List<ICommand>();
        Token token = this.NextToken();
        while (token != null && !(token.TokenType == TokenType.Separator 
             && token.Value.Equals("}")))
        {
            this.PushToken(token);
            commands.Add(this.ParseCommand());
            token = this.NextToken();
        }
        if (token == null)
            throw new ParserException("Expected '}'");
        return new CompositeCommand(commands);
    }

I tested the unclosed command:

        [TestMethod]
        [ExpectedException(typeof(ParserException))]
        public void RaiseIfCompositeCommandNotClosed()
        {
            Parser parser = new Parser("{ a = a+1; a = a+2; ");
            parser.ParseCommand();
        }

And the evaluation of simple composite commands, using if and while commands (no change in those command implementation: they can manage any ICommand, and our new CompositeCommand implements the interface):

        [TestMethod]
        public void ParseAndEvaluateSimpleIfWithCompositeCommand()
        {
            Parser parser = new Parser("if (1) { a = a+1; a = a+2; }");
            ICommand command = parser.ParseCommand();
            Assert.IsNotNull(command);
            Assert.IsInstanceOfType(command, typeof(IfCommand));
            IfCommand ifcmd = (IfCommand)command;
            Assert.IsInstanceOfType(ifcmd.ThenCommand, typeof(CompositeCommand));
            BindingEnvironment environment = new BindingEnvironment();
            environment.SetValue("a", 2);
            command.Execute(environment);
            Assert.AreEqual(5, environment.GetValue("a"));
        }
        [TestMethod]
        public void ParseAndEvaluateSimpleWhileWithCompositeCommand()
        {
            Parser parser = new Parser("while (a) { a = a-1; b=b+1; }");
            ICommand command = parser.ParseCommand();
            Assert.IsNotNull(command);
            Assert.IsInstanceOfType(command, typeof(WhileCommand));
            WhileCommand whilecmd = (WhileCommand)command;
            Assert.IsInstanceOfType(whilecmd.Command, typeof(CompositeCommand));
            BindingEnvironment environment = new BindingEnvironment();
            environment.SetValue("a", 2);
            environment.SetValue("b", 0);
            command.Execute(environment);
            Assert.AreEqual(0, environment.GetValue("a"));
            Assert.AreEqual(2, environment.GetValue("b"));
        }

The above code is the CURRENT version. During the development, and using TDD, I wrote the code by steps, and then, I did some refactoring. But the lesson is: use the tests to guide your development.

All tests in green:

Good code coverage:

You can download the current version from InterpreterStep09.zip. If you want to see all the steps, I commit the code in under trunk/Interpreter in my AjCodeKatas Google code project.

Next steps: foreach command, for command, function declaration, etc…

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

1 Comment »

  1. […] Test-Driven Development — ajlopez @ 10:45 am I’m writing a post series about writing an interpreter using TDD (Test-Driven Development). My intention is to show the use of TDD in our production code. Since my […]

    Pingback by Writing an Application Using TDD (Part 1) Introduction « Angel “Java” Lopez on Blog — February 26, 2011 @ 10:45 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: