# Writing a Simple C ++ Interpreter Using TDD, Part 2

• Tutorial
In the first part, a lexer was written . Next, the development of the parser will be considered.

#### Parser

The parser will be implemented according to the algorithm of the sorting station, since it is quite simple and does not need recursion. The algorithm itself is as follows:

In the beginning, an empty output stream and an empty stack are given. Let's start reading the tokens from the input stream in turn.

• If this is a number, then pass it to the output stream.
• If this is a left associative operator, then we push the tokens from the stack into the output stream until it is empty, or a bracket appears at its top, or an operator with a lower priority.
• If this is an opening bracket, then put it on the stack.
• If this is a closing bracket, then we push the tokens from the stack into the output stream until the opening bracket is detected. Push the opening bracket from the stack, but do not pass it to the output stream. If the stack is empty and the bracket is not found, then we generate an error.

After reaching the end of the input stream, push all the remaining operators on the stack into the output stream. If anything other than operators is found in it, then we generate an error.

Let’s figure out what tests you might need to get started.

• When an empty list is received, an empty list is returned.
• When receiving a list with one number, a list with that number is returned.
• Upon receipt of [1 + 2], [1 2 +] is returned.
• Upon receipt of [1 + 2 + 3], [1 2 + 3 +] is returned, since the + operator is left-associative.
• Upon receipt of [1 * 2 + 3], [1 2 * 3 +] is returned.
• Upon receipt of [1 + 2 * 3], [1 2 3 * +] is returned, since the * operator has a higher priority.

We will deal with brackets and errors later. So, we will write the first test from the list.

``````TEST_CLASS(ParserTests) {
public:
TEST_METHOD(Should_return_empty_list_when_put_empty_list) {
Tokens tokens = Parser::Parse({});
Assert::IsTrue(tokens.empty());
}
};
``````

Like last time, the parser will be a free function in the namespace `Parser`.

``````namespace Parser {
inline Tokens Parse(const Tokens &) { throw std::exception(); }
} // namespace Parser
``````

Make the test pass by applying ({} → nil).

``````inline Tokens Parse(const Tokens &) {
return{};
}
``````

The next test is almost the same as the previous one.

``````    TEST_METHOD(Should_parse_single_number) {
Tokens tokens = Parser::Parse({ Token(1) });
AssertRange::AreEqual({ Token(1) }, tokens);
}
``````

Applying (constant → scalar) we will deal with it.

``````inline Tokens Parse(const Tokens &tokens) {
}
``````

• When an empty list is received, an empty list is returned.
• When receiving a list with one number, a list with that number is returned.
• Upon receipt of [1 + 2], [1 2 +] is returned.
• ...

More interesting, you need to process several tokens at once.

``````    TEST_METHOD(Should_parse_num_plus_num) {
Tokens tokens = Parser::Parse({ Token(1), Token(Operator::Plus), Token(2) });
AssertRange::AreEqual({ Token(1), Token(2), Token(Operator::Plus) }, tokens);
}
``````

To begin with, slightly change the function `Parse`without violating previous tests.

``````inline Tokens Parse(const Tokens &tokens) {
Tokens output;
for(const Token &token : tokens) {
output.push_back(token);
}
return output;
}
``````

Now it’s easy to add a stack for operations and make the test pass.

``````inline Tokens Parse(const Tokens &tokens) {
Tokens output;
Tokens stack;
for(const Token &token : tokens) {
if(token.Type() == TokenType::Number) {
output.push_back(token);
}
else {
stack.push_back(token);
}
}
std::copy(stack.crbegin(), stack.crend(), std::back_inserter(output));
return output;
}
``````

• Upon receipt of [1 + 2], [1 2 +] is returned.
• Upon receipt of [1 + 2 + 3], [1 2 + 3 +] is returned, since the + operator is left-associative.
• ...

``````    TEST_METHOD(Should_parse_two_additions) {
Tokens tokens = Parser::Parse({ Token(1), Token(Operator::Plus), Token(2), Token(Operator::Plus), Token(3) });
AssertRange::AreEqual({ Token(1), Token(2), Token(Operator::Plus), Token(3), Token(Operator::Plus) }, tokens);
}
``````

The test fails, since all operators are located at the end of the list. We will transfer everything from the stack to the output list each time an operator is detected.

``````inline Tokens Parse(const Tokens &tokens) {
Tokens output, stack;
auto popAll = [&]() { while(!stack.empty()) {
output.push_back(stack.back());
stack.pop_back();
}};
for(const Token &token : tokens) {
if(token.Type() == TokenType::Operator) {
popAll();
stack.push_back(token);
continue;
}
output.push_back(token);
}
popAll();
return output;
}
``````

We will deal with the priority of the operators. Add a test for a function that will determine the priority of the operator passed to it.

• Upon receipt of [1 + 2 + 3], [1 2 + 3 +] is returned, since the + operator is left-associative.
• The pairs of operators + - and * / must have equal priorities.
• The priority of the operators + and - should be less than that of * and /
• ...

``````    TEST_METHOD(Should_get_same_precedence_for_operator_pairs) {
Assert::AreEqual(Parser::PrecedenceOf(Operator::Plus), Parser::PrecedenceOf(Operator::Minus));
Assert::AreEqual(Parser::PrecedenceOf(Operator::Mul), Parser::PrecedenceOf(Operator::Div));
}
``````

Add the PrecedenceOf method to the Parser namespace and apply ({} → nil).

``````inline int PrecedenceOf(Operator) { return 0; }
``````

The test passes, go to the next.

``````    TEST_METHOD(Should_get_greater_precedence_for_multiplicative_operators) {
Assert::IsTrue(Parser::PrecedenceOf(Operator::Mul) > Parser::PrecedenceOf(Operator::Plus));
}
``````

We apply (unconditional → if).

``````inline int PrecedenceOf(Operator op) {
return (op == Operator::Mul || op == Operator::Div) ? 1 : 0;
}
``````

• The pairs of operators + - and * / must have equal priorities.
• The priority of the operators + and - should be less than that of * and /
• Upon receipt of [1 * 2 + 3], [1 2 * 3 +] is returned.
• Upon receipt of [1 + 2 * 3], [1 2 3 * +] is returned, since the * operator has a higher priority.

``````    TEST_METHOD(Should_parse_add_and_mul) {
Tokens tokens = Parser::Parse({ Token(1), Token(Operator::Plus), Token(2), Token(Operator::Mul), Token(3) });
AssertRange::AreEqual({ Token(1), Token(2), Token(3), Token(Operator::Mul), Token(Operator::Plus) }, tokens);
}
``````

It does not pass, since the parser should not push operators with lower priority from the stack. Add a predicate to the lambda that pushes operations from the stack that determines when to stop.

``````inline Tokens Parse(const Tokens &tokens) {
Tokens output, stack;
auto popToOutput = [&output, &stack](auto whenToEnd) {
while(!stack.empty() && !whenToEnd(stack.back())) {
output.push_back(stack.back());
stack.pop_back();
}};
for(const Token ¤t : tokens) {
if(current.Type() == TokenType::Operator) {
popToOutput([&](Operator top) { return PrecedenceOf(top) < PrecedenceOf(current); });
stack.push_back(current);
continue;
}
output.push_back(current);
}
popToOutput([](auto) { return false; });
return output;
}
``````

To test the algorithm, add the last test, complicating it a bit. Let's try to process a complex expression with several operations.

``````    TEST_METHOD(Should_parse_complex_experssion) {
// 1 + 2 / 3 - 4 * 5 = 1 2 3 / + 4 5 * -
Tokens tokens = Parser::Parse({
Token(1), Token(Operator::Plus), Token(2), Token(Operator::Div), Token(3),
Token(Operator::Minus), Token(4), Token(Operator::Mul), Token(5)
});
AssertRange::AreEqual({
Token(1), Token(2), Token(3), Token(Operator::Div), Token(Operator::Plus),
Token(4), Token(5), Token(Operator::Mul), Token(Operator::Minus)
}, tokens);
}
``````

He passes right away. Passing tests without writing any code is a dubious practice, but sometimes useful for behavior that you are not completely sure about. A function `Parse`written in a functional style is, of course, short and concise, but poorly extensible. Like last time, select a separate class in the namespace `Detail`and transfer all the parser functionality into it, leaving the function `Parse`as a facade. This is easy enough to do, as you don’t need to be afraid to break something.

``````inline Tokens Parse(const Tokens &tokens) {
Detail::ShuntingYardParser parser(tokens);
parser.Parse();
return parser.Result();
}
``````

Class Detail :: ShuntingYardParser
``````namespace Detail {
class ShuntingYardParser {
public:
ShuntingYardParser(const Tokens &tokens) : m_current(tokens.cbegin()), m_end(tokens.cend()) {}
void Parse() {
for(; m_current != m_end; ++m_current) {
ParseCurrentToken();
}
PopToOutputUntil(StackIsEmpty);
}
const Tokens &Result() const { return m_output; }
private:
static bool StackIsEmpty() { return false; }
void ParseCurrentToken() {
switch(m_current->Type()) {
case TokenType::Operator:
ParseOperator();
break;
case TokenType::Number:
ParseNumber();
break;
default:
throw std::out_of_range("TokenType");
}
}
void ParseOperator() {
PopToOutputUntil([this]() { return PrecedenceOf(m_stack.back()) < PrecedenceOf(*m_current); });
m_stack.push_back(*m_current);
}
void ParseNumber() {
m_output.push_back(*m_current);
}
template
void PopToOutputUntil(T whenToEnd) {
while(!m_stack.empty() && !whenToEnd()) {
m_output.push_back(m_stack.back());
m_stack.pop_back();
}
}
Tokens::const_iterator m_current;
Tokens::const_iterator m_end;
Tokens m_output;
Tokens m_stack;
};
} // namespace Detail
``````

Add parenthesis support. Let's make a list of tests, starting with the simplest to implement.

• Upon receipt of [(1)], returns [1].
• Upon receipt of [(1 + 2) * 3], returns [1 2 + 3 *].
• Upon receipt of [1)], an exception is thrown.
• Upon receipt of [(1], an exception is thrown.

``````    TEST_METHOD(Should_skip_paren_around_number) {
Tokens tokens = Parser::Parse({ Token(Operator::LParen), Token(1), Token(Operator::RParen) });
AssertRange::AreEqual({ Token(1) }, tokens);
}
``````

Since at the moment we do not distinguish brackets from other operators, it does not pass. Make a small change (unconditional → if) to the method `ParseOperator`.

``````    void ParseOperator() {
const Operator currOp = *m_current;
switch(currOp) {
case Operator::LParen:
break;
case Operator::RParen:
break;
default:
PopToOutputUntil([&]() { return PrecedenceOf(m_stack.back()) < PrecedenceOf(currOp); });
m_stack.push_back(*m_current);
break;
}
}
``````

Before adding the next test, we will refactor the currently written tests. Create static instances of tokens for operators and some numbers. This will significantly reduce the amount of code in the tests.

``````static const Token plus(Operator::Plus), minus(Operator::Minus);
static const Token mul(Operator::Mul), div(Operator::Div);
static const Token pLeft(Operator::LParen), pRight(Operator::RParen);
static const Token _1(1), _2(2), _3(3), _4(4), _5(5);
``````

As a result, the next test will look much more compact and understandable.

``````    TEST_METHOD(Should_parse_expression_with_parens_in_beginning) {
Tokens tokens = Parser::Parse({ pLeft, _1, plus, _2, pRight, mul, _3 });
AssertRange::AreEqual({ _1, _2, plus, _3, mul }, tokens);
}
``````

Change the `ParseOperator`class method `ShuntingYardParser`so that it matches the algorithm described above.

``````    void ParseOperator() {
switch(*m_current) {
case Operator::LParen:
m_stack.push_back(*m_current);
break;
case Operator::RParen:
PopToOutputUntil([this]() { return LeftParenOnTop(); });
m_stack.pop_back();
break;
default:
PopToOutputUntil([this]() { return LeftParenOnTop() || OperatorWithLessPrecedenceOnTop(); });
m_stack.push_back(*m_current);
break;
}
}
bool OperatorWithLessPrecedenceOnTop() const {
return PrecedenceOf(m_stack.back()) < PrecedenceOf(*m_current);
}
bool LeftParenOnTop() const {
return static_cast(m_stack.back()) == Operator::LParen;
}
``````

We do not have any check on the closure of parentheses for opening brackets. To test the generation of exceptions, the class `Assert`has a special method `ExpectException`that takes as the template parameter the type of exception that should be thrown.

``````    TEST_METHOD(Should_throw_when_opening_paren_not_found) {
Assert::ExpectException([]() {Parser::Parse({ _1, pRight }); });
}
``````

Add a check for the presence of the opening parenthesis when processing the closing parenthesis.

``````            …
case Operator::RParen:
PopToOutputUntil([this]() { return LeftParenOnTop(); });
if(m_stack.empty() || m_stack.back() != Operator::LParen) {
}
m_stack.pop_back();
break;
``````

Now a test for the absence of a closing parenthesis.

``````    TEST_METHOD(Should_throw_when_closing_paren_not_found) {
Assert::ExpectException([]() {Parser::Parse({ pLeft, _1 }); });
}
``````

This situation can be determined by the presence of opening brackets in the stacks at the end of the parsing.

``````    void Parse() {
for(; m_current != m_end; ++m_current) {
ParseCurrentToken();
}
PopToOutputUntil([this]() {
if(m_stack.back() == Token(Operator::LParen)) {
}
return false;
});
}
``````

So, all tests pass, you can put the code in the class `ShuntingYardParser`in order.

ShuntingYardParser class after refactoring
``````class ShuntingYardParser {
public:
ShuntingYardParser(const Tokens &tokens) : m_current(tokens.cbegin()), m_end(tokens.cend()) {}
void Parse() {
for(; m_current != m_end; ++m_current) {
ParseCurrentToken();
}
PopToOutputUntil([this]() {return StackHasNoOperators(); });
}
const Tokens &Result() const { return m_output; }
private:
void ParseCurrentToken() {
switch(m_current->Type()) {
case TokenType::Operator:
ParseOperator();
break;
case TokenType::Number:
ParseNumber();
break;
default: throw std::out_of_range("TokenType");
}
}
bool StackHasNoOperators() const {
if(m_stack.back() == Token(Operator::LParen)) {
}
return false;
}
void ParseOperator() {
switch(*m_current) {
case Operator::LParen:
PushCurrentToStack();
break;
case Operator::RParen:
PopToOutputUntil([this]() { return LeftParenOnTop(); });
PopLeftParen();
break;
default:
PopToOutputUntil([this]() { return LeftParenOnTop() || OperatorWithLessPrecedenceOnTop(); });
PushCurrentToStack();
break;
}
}
void PushCurrentToStack() {
return m_stack.push_back(*m_current);
}
void PopLeftParen() {
if(m_stack.empty() || m_stack.back() != Operator::LParen) {
}
m_stack.pop_back();
}
bool OperatorWithLessPrecedenceOnTop() const {
return PrecedenceOf(m_stack.back()) < PrecedenceOf(*m_current);
}
bool LeftParenOnTop() const {
return static_cast(m_stack.back()) == Operator::LParen;
}
void ParseNumber() {
m_output.push_back(*m_current);
}
template
void PopToOutputUntil(T whenToEnd) {
while(!m_stack.empty() && !whenToEnd()) {
m_output.push_back(m_stack.back());
m_stack.pop_back();
}
}
Tokens::const_iterator m_current, m_end;
Tokens m_output, m_stack;
};
``````

To be sure, you can write a test to parse a complex expression.

``````    TEST_METHOD(Should_parse_complex_experssion_with_paren) {
// (1+2)*(3/(4-5)) = 1 2 + 3 4 5 - / *
Tokens tokens = Parser::Parse({ pLeft, _1, plus, _2, pRight, mul, pLeft, _3, div, pLeft, _4, minus, _5, pRight, pRight });
AssertRange::AreEqual({ _1, _2, plus, _3, _4, _5, minus, div, mul }, tokens);
}
``````

He immediately passes, as was expected.

All code at the moment:

Interpreter.h
``````#pragma once;
#include
#include
#include
namespace Interpreter {
enum class Operator : wchar_t {
Plus = L'+',
Minus = L'-',
Mul = L'*',
Div = L'/',
LParen = L'(',
RParen = L')',
};
inline std::wstring ToString(const Operator &op) {
return{ static_cast(op) };
}
enum class TokenType {
Operator,
Number
};
inline std::wstring ToString(const TokenType &type) {
switch(type) {
case TokenType::Operator:
return L"Operator";
case TokenType::Number:
return L"Number";
default:
throw std::out_of_range("TokenType");
}
}
class Token {
public:
Token(Operator op) :m_type(TokenType::Operator), m_operator(op) {}
Token(double num) :m_type(TokenType::Number), m_number(num) {}
TokenType Type() const {
return m_type;
}
operator Operator() const {
if(m_type != TokenType::Operator) {
throw std::logic_error("Should be operator token.");
}
return m_operator;
}
operator double() const {
if(m_type != TokenType::Number) {
throw std::logic_error("Should be number token.");
}
return m_number;
}
friend inline bool operator==(const Token &left, const Token &right) {
if(left.m_type == right.m_type) {
switch(left.m_type) {
case Interpreter::TokenType::Operator:
return left.m_operator == right.m_operator;
case Interpreter::TokenType::Number:
return left.m_number == right.m_number;
default:
throw std::out_of_range("TokenType");
}
}
return false;
}
private:
TokenType m_type;
union {
Operator m_operator;
double m_number;
};
};
inline std::wstring ToString(const Token &token) {
switch(token.Type()) {
case TokenType::Number:
return std::to_wstring(static_cast(token));
case TokenType::Operator:
default:
throw std::out_of_range("TokenType");
}
}
typedef std::vector Tokens;
namespace Lexer {
namespace Detail {
class Tokenizer {
public:
Tokenizer(const std::wstring &expr) : m_current(expr.c_str()) {}
void Tokenize() {
if(IsNumber()) {
ScanNumber();
}
else if(IsOperator()) {
ScanOperator();
}
else {
MoveNext();
}
}
}
const Tokens &Result() const {
return m_result;
}
private:
return *m_current == L'\0';
}
bool IsNumber() const {
return iswdigit(*m_current) != 0;
}
void ScanNumber() {
wchar_t *end = nullptr;
m_result.push_back(wcstod(m_current, &end));
m_current = end;
}
bool IsOperator() const {
auto all = { Operator::Plus, Operator::Minus, Operator::Mul, Operator::Div, Operator::LParen, Operator::RParen };
return std::any_of(all.begin(), all.end(), [this](Operator o) {return *m_current == static_cast(o); });
}
void ScanOperator() {
m_result.push_back(static_cast(*m_current));
MoveNext();
}
void MoveNext() {
++m_current;
}
const wchar_t *m_current;
Tokens m_result;
};
} // namespace Detail
inline Tokens Tokenize(const std::wstring &expr) {
Detail::Tokenizer tokenizer(expr);
tokenizer.Tokenize();
}
} // namespace Lexer
namespace Parser {
inline int PrecedenceOf(Operator op) {
return (op == Operator::Mul || op == Operator::Div) ? 1 : 0;
}
namespace Detail {
class ShuntingYardParser {
public:
ShuntingYardParser(const Tokens &tokens) : m_current(tokens.cbegin()), m_end(tokens.cend()) {}
void Parse() {
for(; m_current != m_end; ++m_current) {
ParseCurrentToken();
}
PopToOutputUntil([this]() {return StackHasNoOperators(); });
}
const Tokens &Result() const {
return m_output;
}
private:
void ParseCurrentToken() {
switch(m_current->Type()) {
case TokenType::Operator:
ParseOperator();
break;
case TokenType::Number:
ParseNumber();
break;
default:
throw std::out_of_range("TokenType");
}
}
bool StackHasNoOperators() const {
if(m_stack.back() == Token(Operator::LParen)) {
}
return false;
}
void ParseOperator() {
switch(*m_current) {
case Operator::LParen:
PushCurrentToStack();
break;
case Operator::RParen:
PopToOutputUntil([this]() { return LeftParenOnTop(); });
PopLeftParen();
break;
default:
PopToOutputUntil([this]() { return LeftParenOnTop() || OperatorWithLessPrecedenceOnTop(); });
PushCurrentToStack();
break;
}
}
void PushCurrentToStack() {
return m_stack.push_back(*m_current);
}
void PopLeftParen() {
if(m_stack.empty() || m_stack.back() != Operator::LParen) {
}
m_stack.pop_back();
}
bool OperatorWithLessPrecedenceOnTop() const {
return PrecedenceOf(m_stack.back()) < PrecedenceOf(*m_current);
}
bool LeftParenOnTop() const {
return static_cast(m_stack.back()) == Operator::LParen;
}
void ParseNumber() {
m_output.push_back(*m_current);
}
template
void PopToOutputUntil(T whenToEnd) {
while(!m_stack.empty() && !whenToEnd()) {
m_output.push_back(m_stack.back());
m_stack.pop_back();
}
}
Tokens::const_iterator m_current;
Tokens::const_iterator m_end;
Tokens m_output;
Tokens m_stack;
};
} // namespace Detail
inline Tokens Parse(const Tokens &tokens) {
Detail::ShuntingYardParser parser(tokens);
parser.Parse();
return parser.Result();
}
} // namespace Parser
} // namespace Interpreter
``````

InterpreterTests.cpp
``````#include "stdafx.h"
#include "CppUnitTest.h"
#include "Interpreter.h"
#pragma warning(disable: 4505)
namespace InterpreterTests {
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace Interpreter;
using namespace std;
namespace AssertRange {
template
static void AreEqual(initializer_list expect, const ActualRange &actual) {
auto actualIter = begin(actual);
auto expectIter = begin(expect);
Assert::AreEqual(distance(expectIter, end(expect)), distance(actualIter, end(actual)), L"Size differs.");
for(; expectIter != end(expect) && actualIter != end(actual); ++expectIter, ++actualIter) {
auto message = L"Mismatch in position " + to_wstring(distance(begin(expect), expectIter));
Assert::AreEqual(*expectIter, *actualIter, message.c_str());
}
}
} // namespace AssertRange
static const Token plus(Operator::Plus), minus(Operator::Minus);
static const Token mul(Operator::Mul), div(Operator::Div);
static const Token pLeft(Operator::LParen), pRight(Operator::RParen);
static const Token _1(1), _2(2), _3(3), _4(4), _5(5);
TEST_CLASS(LexerTests) {
public:
TEST_METHOD(Should_return_empty_token_list_when_put_empty_expression) {
Tokens tokens = Lexer::Tokenize(L"");
Assert::IsTrue(tokens.empty());
}
TEST_METHOD(Should_tokenize_single_plus_operator) {
Tokens tokens = Lexer::Tokenize(L"+");
AssertRange::AreEqual({ plus }, tokens);
}
TEST_METHOD(Should_tokenize_single_digit) {
Tokens tokens = Lexer::Tokenize(L"1");
AssertRange::AreEqual({ _1 }, tokens);
}
TEST_METHOD(Should_tokenize_floating_point_number) {
Tokens tokens = Lexer::Tokenize(L"12.34");
AssertRange::AreEqual({ 12.34 }, tokens);
}
TEST_METHOD(Should_tokenize_plus_and_number) {
Tokens tokens = Lexer::Tokenize(L"+12.34");
AssertRange::AreEqual({ plus, Token(12.34) }, tokens);
}
TEST_METHOD(Should_skip_spaces) {
Tokens tokens = Lexer::Tokenize(L" 1 +  12.34  ");
AssertRange::AreEqual({ _1, plus, Token(12.34) }, tokens);
}
Tokens tokens = Lexer::Tokenize(L"1+2*3/(4-5)");
AssertRange::AreEqual({ _1, plus, _2, mul, _3, div, pLeft, _4, minus, _5, pRight }, tokens);
}
};
TEST_CLASS(TokenTests) {
public:
TEST_METHOD(Should_get_type_for_operator_token) {
Token opToken(Operator::Plus);
Assert::AreEqual(TokenType::Operator, opToken.Type());
}
TEST_METHOD(Should_get_type_for_number_token) {
Token numToken(1.2);
Assert::AreEqual(TokenType::Number, numToken.Type());
}
TEST_METHOD(Should_get_operator_code_from_operator_token) {
Token token(Operator::Plus);
Assert::AreEqual(Operator::Plus, token);
}
TEST_METHOD(Should_get_number_value_from_number_token) {
Token token(1.23);
Assert::AreEqual(1.23, token);
}
};
TEST_CLASS(ParserTests) {
public:
TEST_METHOD(Should_return_empty_list_when_put_empty_list) {
Tokens tokens = Parser::Parse({});
Assert::IsTrue(tokens.empty());
}
TEST_METHOD(Should_parse_single_number) {
Tokens tokens = Parser::Parse({ _1 });
AssertRange::AreEqual({ _1 }, tokens);
}
TEST_METHOD(Should_parse_num_plus_num) {
Tokens tokens = Parser::Parse({ _1, plus, _2 });
AssertRange::AreEqual({ _1, _2, plus }, tokens);
}
Tokens tokens = Parser::Parse({ _1, plus, _2, plus, _3 });
AssertRange::AreEqual({ _1, _2, plus, _3, plus }, tokens);
}
TEST_METHOD(Should_get_same_precedence_for_operator_pairs) {
Assert::AreEqual(Parser::PrecedenceOf(Operator::Plus), Parser::PrecedenceOf(Operator::Minus));
Assert::AreEqual(Parser::PrecedenceOf(Operator::Mul), Parser::PrecedenceOf(Operator::Div));
}
TEST_METHOD(Should_get_greater_precedence_for_multiplicative_operators) {
Assert::IsTrue(Parser::PrecedenceOf(Operator::Mul) > Parser::PrecedenceOf(Operator::Plus));
}
Tokens tokens = Parser::Parse({ _1, plus, _2, mul, _3 });
AssertRange::AreEqual({ _1, _2, _3, mul, plus }, tokens);
}
Tokens tokens = Parser::Parse({ _1, plus, _2, div, _3, minus, _4, mul, _5 });
AssertRange::AreEqual({ _1, _2, _3, div, plus, _4, _5, mul, minus }, tokens);
}
TEST_METHOD(Should_skip_parens_around_number) {
Tokens tokens = Parser::Parse({ pLeft, _1, pRight });
AssertRange::AreEqual({ _1 }, tokens);
}
TEST_METHOD(Should_parse_expression_with_parens_in_beginning) {
Tokens tokens = Parser::Parse({ pLeft, _1, plus, _2, pRight, mul, _3 });
AssertRange::AreEqual({ _1, _2, plus, _3, mul }, tokens);
}
TEST_METHOD(Should_throw_when_opening_paren_not_found) {
Assert::ExpectException([]() {Parser::Parse({ _1, pRight }); });
}
TEST_METHOD(Should_throw_when_closing_paren_not_found) {
Assert::ExpectException([]() {Parser::Parse({ pLeft, _1 }); });
}
// (1+2)*(3/(4-5)) = 1 2 + 3 4 5 - / *
Tokens tokens = Parser::Parse({ pLeft, _1, plus, _2, pRight, mul, pLeft, _3, div, pLeft, _4, minus, _5, pRight, pRight });
AssertRange::AreEqual({ _1, _2, plus, _3, _4, _5, minus, div, mul }, tokens);
}
};
}
``````

Code for the article on GitHub . The names of the commits and their order correspond to the tests and refactoring described above. The end of the second part corresponds to the label “End_second_part”.

In the next part, we will consider the development of a computer and a facade for the entire interpreter, as well as refactoring the entire code to eliminate duplication.