3
\$\begingroup\$

I am trying to create a mustache interpreter. I have started implementing the lexer parts.

Here are the classes:

Lexer.h

#ifndef LEXER_H
#define LEXER_H

#include <vector>
#include <string>
#include <stdexcept>      // std::out_of_range
#include <cstdio>       //EOF
#include "LexerElement.h"

namespace mustache {
    constexpr size_t min_string_len = 4;

    enum class Mode {
        TEXT,
        TAG_END_SEARCH
    };

    class Lexer {
        std::string m_text;

        std::string m_TagStart{"{{"};
        std::string m_TagEnd{"}}"};

        std::size_t m_position{0};
        std::size_t m_position_marker{0};
        std::string::size_type m_size;

        std::vector<LexerElement> m_elements;

        const char getChar() const noexcept;

        void charBack() noexcept {
            m_position--;
        }

        void charNext() noexcept {
            m_position++;
        }

        void mark_position() noexcept {
            m_position_marker = m_position;
        }

        void reset_position_to_marker() noexcept {
            m_position = m_position_marker;
        }

        bool isTagPart(std::string part) noexcept;

        bool isTagStart() noexcept {
            return isTagPart(m_TagStart);
        }

        bool isTagEnd() noexcept {
            return isTagPart(m_TagEnd);
        }

        void lex();
    public:
        Lexer(const std::string &str);
        virtual ~Lexer();

        void lex(const std::string &str);

        void dump_elements() const ;

        std::vector<LexerElement> getElements() const noexcept;

    };
}
#endif /* LEXER_H */

Lexer.cpp

#include "Lexer.h"
#include <iostream>

namespace mustache {

    Lexer::Lexer(const std::string &str) {
        lex(str);
    }

    Lexer::~Lexer() {
    }

    void Lexer::dump_elements() const {

        for (const auto &i : m_elements) {
            std::cout << i.getData() << '\n';
        }
    }

    std::vector<LexerElement> Lexer::getElements() const noexcept {
        return m_elements;
    }

    const char Lexer::getChar() const noexcept {
        try {
            return m_text.at(m_position);
        } catch (const std::out_of_range& oor) {
            return EOF;
        }
    }

    bool Lexer::isTagPart(std::string part) noexcept {
        mark_position();
        bool is_tag = false;

        for (const char &s : part) {
            if (s == getChar()) {
                charNext();
                is_tag = true;
                continue;
            }

            is_tag = false;
            break;
        }

        if (is_tag == false) {
            reset_position_to_marker();
        }
        return is_tag;
    }

    void Lexer::lex(const std::string &str) {
        if (str.size() <= min_string_len) {
            throw "String too small";
        }

        m_text = str;
        m_size = str.size();

        lex();
    }

    void Lexer::lex() {
        Mode mode = Mode::TEXT;
        size_t pos = 0;

        while (getChar() != EOF) {
            switch (mode) {
                case Mode::TEXT:
                    if (isTagStart()) {
                        if (m_position > 0) {
                            size_t len = m_position - m_TagEnd.length() - pos;
                            m_elements.emplace_back(m_text.substr(pos, len));

                            pos = m_position - m_TagEnd.length();
                        }

                        mode = Mode::TAG_END_SEARCH;
                    }
                    break;

                case Mode::TAG_END_SEARCH:
                    if (isTagEnd()) {
                        m_elements.emplace_back(m_text.substr(pos, m_position - pos), LexerElementTypes::TAG);

                        pos = m_position;
                        mode = Mode::TEXT;
                    }
                    break;
            }

            charNext();
        }
    }
}

LexerElement.h

#ifndef LEXERELEMENT_H
#define LEXERELEMENT_H

#include <string>

namespace mustache {

    enum class LexerElementTypes {
        TAG,
        TEXT
    };

    class LexerElement {
        std::string m_data;
        LexerElementTypes m_type;
    public:

        LexerElement(std::string data, LexerElementTypes m = LexerElementTypes::TEXT) : m_data(data), m_type(m) {
        };

        std::string getData() const {
            return m_data;
        }
    };
}
#endif /* LEXERELEMENT_H */

The full project can be seen on github. Any advice on how to improve the makefile would be greatly appreciated.

makefile

BIN=mustachecpp
BUILD_DIR=./build
OBJ_DIR=./obj
TARGET=$(BUILD_DIR)/$(BIN)
CFLAGS=-std=c++14 -Wall -g -Werror
LDFLAGS=
LDLIBS=
CC=g++
SRC_DIR=./src
SRC=$(shell find $(SRC_DIR) -name '*.cpp')
HEADERS=$(shell find $(SRC_DIR) -name '*.h')
#https://www.gnu.org/software/make/manual/html_node/Text-Functions.html
OBJ=$(addprefix $(OBJ_DIR)/,$(notdir $(patsubst %.cpp, %.o, $(SRC))))
TEST_MAIN=$(OBJ_DIR)/tests-main.o

#http://stackoverflow.com/a/1951111/8715
dir_guard=@mkdir -p $(@D)


.PHONY: clean

all: $(TARGET)

$(TARGET): $(OBJ)
    $(dir_guard)
    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)

tests: $(TEST_MAIN) $(OBJ) tests/LexerTest.cpp
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(BUILD_DIR)/$@ $^ $(LDLIBS)


#To cause recompile when .h files are change
$(OBJ): $(SRC) $(HEADERS)
    $(dir_guard)
    $(CC) $(CFLAGS) -c $? $(LDLIBS)
#Put all the object files in the correct directory
    @mv *.o $(OBJ_DIR)


$(TEST_MAIN): tests/tests-main.cpp
    $(CC) $(CFLAGS) -c -o $(TEST_MAIN) $^


clean:
    rm -rf $(BUILD_DIR)/* $(OBJ_DIR)/*
\$\endgroup\$
2
\$\begingroup\$

A Makefile review

Your object files should not each depend on all the headers. You'll find that doing so will not be a problem now, but will waste a lot of your time as your project grows. Instead, generate the dependencies automatically - e.g. following Paul D. Smith's paper.

Use CXX and CXXFLAGS for C++ compilation, not CC and CFLAGS.

It's best to run make in the target directory, and (if necessary) use VPATH to find the sources. If you need to generate several targets using the same Makefile (e.g. for multi-platform builds), you can do that:

#!/bin/sh
make -C arm -f ../Makefile ARCH=arm
make -C x86 -f ../Makefile ARCH=x86
make -C sparc -f ../Makefile ARCH=sparc

If you must insist on doing it the other way around, at least save yourself duplicating built-in $(COMPILE.cc) and $(LINK.cc).


Suggested Makefile, as a starting point:

ifndef TOP
# Re-run Make in the build directory
OBJDIR=build

.PHONY: $(OBJDIR)
$(OBJDIR):
    +@test -d $@ || mkdir -p $@
    +@test -d $@/.d || mkdir -p $@/.d
    +@$(MAKE) --no-print-directory -C $@ -f ../Makefile TOP=.. $(MAKECMDGOALS)

Makefile: ;

%:: $(OBJDIR) ; : 

.PHONY: clean
clean:
    $(RM) -r $(OBJDIR)

.SUFFIXES:

else
# We're in the build directory

OBJS = Lexer.o LexerTest.o tests-main.o

LexerTest: $(OBJS)
    $(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@

VPATH = $(TOP)/src $(TOP)/tests

default: $(OBJS)


#Dependency generation, from http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/

DEPDIR := .d
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td

CXXFLAGS += $(DEPFLAGS)

POSTCOMPILE = @mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d && touch $@

%.o : %.c
%.o : %.c $(DEPDIR)/%.d
    $(COMPILE.c) $(OUTPUT_OPTION) $<
    $(POSTCOMPILE)

%.o : %.cpp
%.o : %.cpp $(DEPDIR)/%.d
    $(COMPILE.cc) $(OUTPUT_OPTION) $<
    $(POSTCOMPILE)

$(DEPDIR)/%.d: ;
.PRECIOUS: $(DEPDIR)/%.d

include $(wildcard $(patsubst %,$(DEPDIR)/%.d,$(basename $(SRCS))))

endif
\$\endgroup\$
  • \$\begingroup\$ I will try to read that paper carefully and see if I can understand all the rules in the makefile. Thanks! \$\endgroup\$ – AntonioCS Sep 2 '17 at 21:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.