Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -----------------------------------------------------------------------
- lexer.h
- -----------------------------------------------------------------------
- #pragma once
- #include <sstream>
- #include <stdexcept>
- #include <string>
- #include <variant>
- #include <vector>
- #include <map>
- namespace parse {
- namespace token_type {
- struct Number {int value;};
- struct Id {std::string value;};
- struct Char {char value;};
- struct String {std::string value;};
- struct Class {};
- struct Return {};
- struct If {};
- struct Else {};
- struct Def {};
- struct Newline {};
- struct Print {};
- struct Indent {};
- struct Dedent {};
- struct Eof {};
- struct And {};
- struct Or {};
- struct Not {};
- struct Eq {};
- struct NotEq {};
- struct LessOrEq {};
- struct GreaterOrEq {};
- struct None {};
- struct True {};
- struct False {};
- }//end namespace token_type
- using TokenBase = std::variant<token_type::Number,
- token_type::Id,
- token_type::Char,
- token_type::String,
- token_type::Class,
- token_type::Return,
- token_type::If,
- token_type::Else,
- token_type::Def,
- token_type::Newline,
- token_type::Print,
- token_type::Indent,
- token_type::Dedent,
- token_type::And,
- token_type::Or,
- token_type::Not,
- token_type::Eq,
- token_type::NotEq,
- token_type::LessOrEq,
- token_type::GreaterOrEq,
- token_type::None,
- token_type::True,
- token_type::False,
- token_type::Eof>;
- struct Token : TokenBase {
- using TokenBase::TokenBase;
- template <typename T>
- [[nodiscard]] bool Is() const {return std::holds_alternative<T>(*this);}
- template <typename T>
- [[nodiscard]] const T& As() const {return std::get<T>(*this);}
- template <typename T>
- [[nodiscard]] const T* TryAs() const {return std::get_if<T>(this);}
- };
- bool operator==(const Token& lhs, const Token& rhs);
- bool operator!=(const Token& lhs, const Token& rhs);
- std::ostream& operator<<(std::ostream& os, const Token& rhs);
- class LexerError : public std::runtime_error {
- public:
- using std::runtime_error::runtime_error;
- };
- class Lexer {
- public:
- explicit Lexer(std::istream& input);
- [[nodiscard]] const Token& CurrentToken() const;
- Token NextToken();
- template <typename T>
- const T& Expect() const {
- using namespace std::literals;
- if (CurrentToken().Is<T>()) {
- return CurrentToken().As<T>();
- } else {
- throw LexerError("expectation error");
- }
- }
- template <typename T, typename U>
- void Expect(const U& value) const {
- using namespace std::literals;
- try {
- if (CurrentToken().As<T>().value != value) {
- throw LexerError("expectation error");
- } else {
- return;
- }
- } catch (std::bad_variant_access&) {
- throw LexerError("expectation error");
- }
- }
- template <typename T>
- const T& ExpectNext() {
- NextToken();
- return Expect<T>();
- }
- template <typename T, typename U>
- void ExpectNext(const U& value) {
- NextToken();
- Expect<T>(value);
- }
- private:
- std::map<std::string, Token> keywords_ {
- {"class", token_type::Class{}},
- {"return", token_type::Return{}},
- {"if", token_type::If{}},
- {"else", token_type::Else{}},
- {"def", token_type::Def{}},
- {"print", token_type::Print{}},
- {"or", token_type::Or{}},
- {"None", token_type::None{}},
- {"and", token_type::And{}},
- {"not", token_type::Not{}},
- {"True", token_type::True{}},
- {"False", token_type::False{}}
- };
- int spaces_ = 0;
- std::vector<Token> tokens_;
- std::vector<Token>::const_iterator current_token_;
- void parse_number(std::istream& input);
- void parse_char(std::istream& input);
- void parse_string(std::istream& input);
- void parse_operator(std::istream& input);
- void parse_id(std::istream& input);
- void parse_newline(std::istream& input);
- void parse_indent(std::istream& input);
- void pass_spaces(std::istream& input);
- void pass_comment(std::istream& input);
- std::vector<Token>::const_iterator parse_tokens(std::istream& input);
- };
- }//end namespace parse
- -----------------------------------------------------------------------
- lexer.cpp
- -----------------------------------------------------------------------
- #include "lexer.h"
- #include <algorithm>
- #include <charconv>
- #include <unordered_map>
- using namespace std;
- namespace parse {
- bool operator==(const Token& lhs, const Token& rhs) {
- using namespace token_type;
- if (lhs.index() != rhs.index()) {return false;}
- if (lhs.Is<Char>()) {return lhs.As<Char>().value == rhs.As<Char>().value;}
- if (lhs.Is<Number>()) {return lhs.As<Number>().value == rhs.As<Number>().value;}
- if (lhs.Is<String>()) {return lhs.As<String>().value == rhs.As<String>().value;}
- if (lhs.Is<Id>()) {return lhs.As<Id>().value == rhs.As<Id>().value;}
- return true;
- }
- bool operator!=(const Token& lhs, const Token& rhs) {return !(lhs == rhs);}
- std::ostream& operator<<(std::ostream& os, const Token& rhs) {
- using namespace token_type;
- #define VALUED_OUTPUT(type) \
- if (auto p = rhs.TryAs<type>()) return os << #type << '{' << p->value << '}';
- VALUED_OUTPUT(Number);
- VALUED_OUTPUT(Id);
- VALUED_OUTPUT(String);
- VALUED_OUTPUT(Char);
- #undef VALUED_OUTPUT
- #define UNVALUED_OUTPUT(type) \
- if (rhs.Is<type>()) return os << #type;
- UNVALUED_OUTPUT(Class);
- UNVALUED_OUTPUT(Return);
- UNVALUED_OUTPUT(If);
- UNVALUED_OUTPUT(Else);
- UNVALUED_OUTPUT(Def);
- UNVALUED_OUTPUT(Newline);
- UNVALUED_OUTPUT(Print);
- UNVALUED_OUTPUT(Indent);
- UNVALUED_OUTPUT(Dedent);
- UNVALUED_OUTPUT(And);
- UNVALUED_OUTPUT(Or);
- UNVALUED_OUTPUT(Not);
- UNVALUED_OUTPUT(Eq);
- UNVALUED_OUTPUT(NotEq);
- UNVALUED_OUTPUT(LessOrEq);
- UNVALUED_OUTPUT(GreaterOrEq);
- UNVALUED_OUTPUT(None);
- UNVALUED_OUTPUT(True);
- UNVALUED_OUTPUT(False);
- UNVALUED_OUTPUT(Eof);
- #undef UNVALUED_OUTPUT
- return os << "Unknown token :("sv;
- }
- Lexer::Lexer(std::istream& input) : current_token_(parse_tokens(input)) {}
- const Token& Lexer::CurrentToken() const {return *current_token_;}
- Token Lexer::NextToken() {
- if ((current_token_ + 1) == tokens_.end()) {
- return token_type::Eof{};
- } else {
- return *++current_token_;
- }
- }
- void Lexer::parse_number(std::istream& input) {
- char curr;
- curr = input.peek();
- if (std::isdigit(curr)) {
- string parsed;
- while (input.get(curr)) {
- if (std::isdigit(curr)) {
- parsed.push_back(curr);
- } else {
- input.putback(curr);
- break;
- }
- }
- tokens_.emplace_back(token_type::Number{std::stoi(parsed)});
- }
- }
- void Lexer::parse_char(std::istream& input) {
- char curr;
- curr = input.peek();
- if (std::ispunct(curr)) {
- if (curr == '#') {
- pass_comment(input);
- return;
- }
- input.get(curr);
- tokens_.emplace_back(token_type::Char{curr});
- }
- }
- void Lexer::parse_string(std::istream& input) {
- char quote;
- input.get(quote);
- if (quote == '\'' || quote == '\"') {
- auto it = std::istreambuf_iterator<char>(input);
- auto end = std::istreambuf_iterator<char>();
- std::string str;
- while (it != end) {
- const char curr = *it;
- if (curr == quote) {
- ++it;
- break;
- } else if (curr == '\\') {
- ++it;
- if (it == end) {
- throw std::logic_error("unable to parse. string");
- }
- const char escaped = *(it);
- switch (escaped) {
- case 'n':
- str.push_back('\n');
- break;
- case 't':
- str.push_back('\t');
- break;
- case 'r':
- str.push_back('\r');
- break;
- case '"':
- str.push_back('"');
- break;
- case '\'':
- str.push_back('\'');
- break;
- case '\\':
- str.push_back('\\');
- break;
- default:
- throw std::invalid_argument("invalid escape char");
- }
- } else if (curr == '\n' || curr == '\r') {
- throw std::logic_error("invalid eof"s);
- } else {
- str.push_back(curr);
- }
- ++it;
- }
- tokens_.emplace_back(token_type::String{str});
- } else {
- input.putback(quote);
- }
- }
- void Lexer::parse_operator(std::istream& input) {
- char curr;
- input.get(curr);
- char next = input.peek();
- if (curr == '=' && next == '=') {
- input.get();
- tokens_.emplace_back(token_type::Eq{});
- } else if (curr == '!' && next == '=') {
- input.get();
- tokens_.emplace_back(token_type::NotEq{});
- } else if (curr == '<' && next == '=') {
- input.get();
- tokens_.emplace_back(token_type::LessOrEq{});
- } else if (curr == '>' && next == '=') {
- input.get();
- tokens_.emplace_back(token_type::GreaterOrEq{});
- } else {
- input.putback(curr);
- }
- }
- void Lexer::parse_id(std::istream& input) {
- char curr;
- curr = input.peek();
- if (std::isalpha(curr) || curr == '_') {
- std::string parsed;
- while (input.get(curr)) {
- if (std::isalnum(curr) || curr == '_') {
- parsed.push_back(curr);
- } else {
- input.putback(curr);
- break;
- }
- }
- if (keywords_.find(parsed) != keywords_.end()) {
- tokens_.push_back(keywords_.at(parsed));
- } else {
- tokens_.emplace_back(token_type::Id{parsed});
- }
- }
- }
- void Lexer::parse_newline(std::istream& input) {
- char curr;
- input.get(curr);
- if (curr == '\n') {
- if (!tokens_.empty() && tokens_.back() != token_type::Newline{}) {
- tokens_.emplace_back(token_type::Newline{});
- }
- parse_indent(input);
- } else {
- input.putback(curr);
- }
- }
- void Lexer::parse_indent(std::istream& input) {
- int spaces = 0;
- char curr;
- while (input.get(curr) && curr == ' ') {
- ++spaces;
- }
- if (input.rdstate() != std::ios_base::eofbit) {
- input.putback(curr);
- }
- char next = input.peek();
- if (next == '\n' || next == '#') {
- return;
- }
- int indents = (spaces - spaces_) / 2;
- spaces_ = spaces;
- Token indent;
- if (indents > 0) {
- indent = token_type::Indent{};
- } else if (indents < 0) {
- indent = token_type::Dedent{};
- }
- for (int i = 0; i < std::abs(indents); ++i) {
- tokens_.emplace_back(indent);
- }
- }
- void Lexer::pass_spaces(std::istream& input) {
- while (input.peek() == ' ') {
- input.get();
- }
- }
- void Lexer::pass_comment(std::istream& input) {
- char curr;
- input.get(curr);
- if (curr == '#') {
- string comment;
- getline(input, comment, '\n');
- if (input.rdstate() != std::ios_base::eofbit) {
- input.putback('\n');
- }
- } else {
- input.putback(curr);
- }
- }
- std::vector<Token>::const_iterator Lexer::parse_tokens(std::istream& input) {
- char curr;
- while (input.get(curr)) {
- input.putback(curr);
- parse_newline(input);
- parse_string(input);
- parse_id(input);
- parse_operator(input);
- parse_char(input);
- parse_number(input);
- pass_spaces(input);
- }
- if (!tokens_.empty() && tokens_.back() != token_type::Newline{} && tokens_.back() != token_type::Dedent{}) {
- tokens_.emplace_back(token_type::Newline{});
- }
- tokens_.emplace_back(token_type::Eof{});
- return tokens_.begin();
- }
- }//end namespace parse
- -----------------------------------------------------------------------
- main.cpp
- -----------------------------------------------------------------------
- #include "lexer.h"
- #include "test_runner_p.h"
- #include <iostream>
- namespace parse {
- void RunOpenLexerTests(TestRunner& tr);
- }
- int main() {
- try {
- TestRunner tr;
- parse::RunOpenLexerTests(tr);
- parse::Lexer lexer(std::cin);
- parse::Token t;
- while ((t = lexer.CurrentToken()) != parse::token_type::Eof{}) {
- std::cout << t << std::endl;
- lexer.NextToken();
- }
- } catch (const std::exception& e) {
- std::cerr << e.what();
- return 1;
- }
- }
- -----------------------------------------------------------------------
- lexer_test_open.cpp
- -----------------------------------------------------------------------
- #include "lexer.h"
- #include "test_runner_p.h"
- #include <sstream>
- #include <string>
- using namespace std;
- namespace parse {
- namespace {
- void TestSimpleAssignment() {
- istringstream input("x = 42\n"s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{42}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- }
- void TestKeywords() {
- istringstream input("class return if else def print or None and not True False"s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Class{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Return{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::If{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Else{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Def{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Print{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Or{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::None{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::And{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Not{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::True{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::False{}));
- }
- void TestNumbers() {
- istringstream input("42 15 -53"s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Number{42}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{15}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'-'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{53}));
- }
- void TestIds() {
- istringstream input("x _42 big_number Return Class dEf"s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"_42"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"big_number"s}));
- ASSERT_EQUAL(lexer.NextToken(),
- Token(token_type::Id{"Return"s})); // keywords are case-sensitive
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"Class"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"dEf"s}));
- }
- void TestStrings() {
- istringstream input(
- R"('word' "two words" 'long string with a double quote " inside' "another long string with single quote ' inside")"s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::String{"word"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::String{"two words"s}));
- ASSERT_EQUAL(lexer.NextToken(),
- Token(token_type::String{"long string with a double quote \" inside"s}));
- ASSERT_EQUAL(lexer.NextToken(),
- Token(token_type::String{"another long string with single quote ' inside"s}));
- }
- void TestOperations() {
- istringstream input("+-*/= > < != == <> <= >="s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Char{'+'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'-'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'*'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'/'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'>'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'<'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::NotEq{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eq{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'<'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'>'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::LessOrEq{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::GreaterOrEq{}));
- }
- void TestIndentsAndNewlines() {
- istringstream input(R"(
- no_indent
- indent_one
- indent_two
- indent_three
- indent_three
- indent_three
- indent_two
- indent_one
- indent_two
- no_indent
- )"s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Id{"no_indent"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Indent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"indent_one"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Indent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"indent_two"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Indent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"indent_three"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"indent_three"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"indent_three"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Dedent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"indent_two"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Dedent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"indent_one"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Indent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"indent_two"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Dedent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Dedent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"no_indent"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- }
- void TestEmptyLinesAreIgnored() {
- istringstream input(R"(
- x = 1
- y = 2
- z = 3
- )"s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{1}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Indent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"y"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{2}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"z"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{3}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Dedent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- }
- void TestMythonProgram() {
- istringstream input(R"(
- x = 4
- y = "hello"
- class Point:
- def __init__(self, x, y):
- self.x = x
- self.y = y
- def __str__(self):
- return str(x) + ' ' + str(y)
- p = Point(1, 2)
- print str(p)
- )"s);
- Lexer lexer(input);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{4}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"y"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::String{"hello"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Class{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"Point"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{':'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Indent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Def{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"__init__"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'('}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"self"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{','}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{','}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"y"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{')'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{':'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Indent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"self"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'.'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"self"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'.'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"y"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"y"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Dedent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Def{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"__str__"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'('}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"self"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{')'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{':'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Indent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Return{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"str"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'('}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{')'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'+'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::String{" "s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'+'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"str"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'('}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"y"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{')'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Dedent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Dedent{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"p"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'='}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"Point"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'('}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{1}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{','}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Number{2}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{')'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Print{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"str"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{'('}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"p"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Char{')'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- }
- void TestExpect() {
- istringstream is("bugaga"s);
- Lexer lex(is);
- ASSERT_DOESNT_THROW(lex.Expect<token_type::Id>());
- ASSERT_EQUAL(lex.Expect<token_type::Id>().value, "bugaga"s);
- ASSERT_DOESNT_THROW(lex.Expect<token_type::Id>("bugaga"s));
- ASSERT_THROWS(lex.Expect<token_type::Id>("widget"s), LexerError);
- ASSERT_THROWS(lex.Expect<token_type::Return>(), LexerError);
- }
- void TestExpectNext() {
- istringstream is("+ bugaga + def 52"s);
- Lexer lex(is);
- ASSERT_EQUAL(lex.CurrentToken(), Token(token_type::Char{'+'}));
- ASSERT_DOESNT_THROW(lex.ExpectNext<token_type::Id>());
- ASSERT_DOESNT_THROW(lex.ExpectNext<token_type::Char>('+'));
- ASSERT_THROWS(lex.ExpectNext<token_type::Newline>(), LexerError);
- ASSERT_THROWS(lex.ExpectNext<token_type::Number>(57), LexerError);
- }
- void TestAlwaysEmitsNewlineAtTheEndOfNonemptyLine() {
- {
- istringstream is("a b"s);
- Lexer lexer(is);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Id{"a"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"b"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- }
- {
- istringstream is("+"s);
- Lexer lexer(is);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Char{'+'}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- }
- }
- void TestCommentsAreIgnored() {
- {
- istringstream is(R"(# comment
- )"s);
- Lexer lexer(is);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Eof{}));
- }
- {
- istringstream is(R"(# comment
- )"s);
- Lexer lexer(is);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Eof{}));
- }
- {
- istringstream is(R"(# comment
- x #another comment
- abc#
- '#'
- "#123"
- #)"s);
- Lexer lexer(is);
- ASSERT_EQUAL(lexer.CurrentToken(), Token(token_type::Id{"x"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Id{"abc"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::String{"#"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::String{"#123"s}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Newline{}));
- ASSERT_EQUAL(lexer.NextToken(), Token(token_type::Eof{}));
- }
- }
- }//end namespace
- void RunOpenLexerTests(TestRunner& tr) {
- RUN_TEST(tr, parse::TestSimpleAssignment);
- RUN_TEST(tr, parse::TestKeywords);
- RUN_TEST(tr, parse::TestNumbers);
- RUN_TEST(tr, parse::TestIds);
- RUN_TEST(tr, parse::TestStrings);
- RUN_TEST(tr, parse::TestOperations);
- RUN_TEST(tr, parse::TestIndentsAndNewlines);
- RUN_TEST(tr, parse::TestEmptyLinesAreIgnored);
- RUN_TEST(tr, parse::TestExpect);
- RUN_TEST(tr, parse::TestExpectNext);
- RUN_TEST(tr, parse::TestMythonProgram);
- RUN_TEST(tr, parse::TestAlwaysEmitsNewlineAtTheEndOfNonemptyLine);
- RUN_TEST(tr, parse::TestCommentsAreIgnored);
- }
- }//end namespace parse
- -----------------------------------------------------------------------
- test_runner_p.h
- -----------------------------------------------------------------------
- #pragma once
- #include <iostream>
- #include <map>
- #include <set>
- #include <sstream>
- #include <stdexcept>
- #include <string>
- #include <unordered_map>
- #include <vector>
- namespace TestRunnerPrivate {
- template <class Map>
- std::ostream& PrintMap(std::ostream& os, const Map& m) {
- os << "{";
- bool first = true;
- for (const auto& kv : m) {
- if (!first) {
- os << ", ";
- }
- first = false;
- os << kv.first << ": " << kv.second;
- }
- return os << "}";
- }
- }//end namespace TestRunnerPrivate
- template <class T>
- std::ostream& operator<<(std::ostream& os, const std::vector<T>& s) {
- os << "{";
- bool first = true;
- for (const auto& x : s) {
- if (!first) {
- os << ", ";
- }
- first = false;
- os << x;
- }
- return os << "}";
- }
- template <class T>
- std::ostream& operator<<(std::ostream& os, const std::set<T>& s) {
- os << "{";
- bool first = true;
- for (const auto& x : s) {
- if (!first) {
- os << ", ";
- }
- first = false;
- os << x;
- }
- return os << "}";
- }
- template <class K, class V>
- std::ostream& operator<<(std::ostream& os, const std::map<K, V>& m) {
- return TestRunnerPrivate::PrintMap(os, m);
- }
- template <class K, class V>
- std::ostream& operator<<(std::ostream& os, const std::unordered_map<K, V>& m) {
- return TestRunnerPrivate::PrintMap(os, m);
- }
- template <class T, class U>
- void AssertEqual(const T& t, const U& u, const std::string& hint = {}) {
- if (!(t == u)) {
- std::ostringstream os;
- os << "Assertion failed: " << t << " != " << u;
- if (!hint.empty()) {
- os << " hint: " << hint;
- }
- throw std::runtime_error(os.str());
- }
- }
- inline void Assert(bool b, const std::string& hint) {
- AssertEqual(b, true, hint);
- }
- class TestRunner {
- public:
- template <class TestFunc>
- void RunTest(TestFunc func, const std::string& test_name) {
- try {
- func();
- std::cerr << test_name << " OK" << std::endl;
- } catch (std::exception& e) {
- ++fail_count;
- std::cerr << test_name << " fail: " << e.what() << std::endl;
- } catch (...) {
- ++fail_count;
- std::cerr << "Unknown exception caught" << std::endl;
- }
- }
- ~TestRunner() {
- std::cerr.flush();
- if (fail_count > 0) {
- std::cerr << fail_count << " unit tests failed. Terminate" << std::endl;
- exit(1);
- }
- }
- private:
- int fail_count = 0;
- };
- #ifndef FILE_NAME
- #define FILE_NAME __FILE__
- #endif
- #define ASSERT_EQUAL(x, y) \
- { \
- std::ostringstream __assert_equal_private_os; \
- __assert_equal_private_os << #x << " != " << #y << ", " << FILE_NAME << ":" << __LINE__; \
- AssertEqual(x, y, __assert_equal_private_os.str()); \
- }
- #define ASSERT(x) \
- { \
- std::ostringstream __assert_private_os; \
- __assert_private_os << #x << " is false, " << FILE_NAME << ":" << __LINE__; \
- Assert(static_cast<bool>(x), __assert_private_os.str()); \
- }
- #define RUN_TEST(tr, func) tr.RunTest(func, #func)
- #define ASSERT_THROWS(expr, expected_exception) \
- { \
- bool __assert_private_flag = true; \
- try { \
- expr; \
- __assert_private_flag = false; \
- } catch (expected_exception&) { \
- } catch (...) { \
- std::ostringstream __assert_private_os; \
- __assert_private_os << "Expression " #expr \
- " threw an unexpected exception" \
- " " FILE_NAME ":" \
- << __LINE__; \
- Assert(false, __assert_private_os.str()); \
- } \
- if (!__assert_private_flag) { \
- std::ostringstream __assert_private_os; \
- __assert_private_os << "Expression " #expr \
- " is expected to throw " #expected_exception " " FILE_NAME ":" \
- << __LINE__; \
- Assert(false, __assert_private_os.str()); \
- } \
- }
- #define ASSERT_DOESNT_THROW(expr) \
- try { \
- expr; \
- } catch (...) { \
- std::ostringstream __assert_private_os; \
- __assert_private_os << "Expression " #expr \
- " threw an unexpected exception" \
- " " FILE_NAME ":" \
- << __LINE__; \
- Assert(false, __assert_private_os.str()); \
- }
- #define EOF_GUARD
Advertisement
Add Comment
Please, Sign In to add comment