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