diff options
| Commit | 93cd8cac8a616ae13bd42dcbee0bb9da278eb49d (patch) | |
|---|---|---|
| Author | 2018-11-07 14:26:16 +0200 | |
| Committer | 2018-11-07 14:26:16 +0200 | |
| Files | ce8b306af9c36d6443fe2fc34526223e44c804cf | |
| IPL-93cd8cac8a616ae13bd42dcbee0bb9da278eb49d.tar IPL-93cd8cac8a616ae13bd42dcbee0bb9da278eb49d.tar.gz IPL-93cd8cac8a616ae13bd42dcbee0bb9da278eb49d.zip | ||
| Parent | 47e8c6f331525f01f150ba80a3557a18618b0cf5 (diff) | |
Solution to first homework (#25)
* Add token types enum for Lexer
* Local test if I've correctly switched from https to ssh
* Adds initial Lexer logic; Reuses JSImpl's Lexer logic; Still needs refactoring;
* Adds README.md
* Adds README.md
* Adds tokenization of strings and a few operators
* Adds some operators
* Adds highlighter and sample for future testing
* Adds runes & fixes double whitespace bug
* API fix, premake, solution
* Update README.md
* Delete HL.sln
* Delete HL.vcxproj
10 files changed, 1415 insertions, 0 deletions
| -rw-r--r-- | homework/81013/README.md | 1 | ||||
| -rw-r--r-- | homework/81013/go2html/CommonTypes.h | 37 | ||||
| -rw-r--r-- | homework/81013/go2html/Highlighter.cpp | 219 | ||||
| -rw-r--r-- | homework/81013/go2html/Highlighter.h | 8 | ||||
| -rw-r--r-- | homework/81013/go2html/Lexer.cpp | 668 | ||||
| -rw-r--r-- | homework/81013/go2html/Lexer.h | 125 | ||||
| -rw-r--r-- | homework/81013/go2html/README.md | 8 | ||||
| -rw-r--r-- | homework/81013/go2html/main.cpp | 19 | ||||
| -rw-r--r-- | homework/81013/go2html/premake5.lua | 22 | ||||
| -rw-r--r-- | homework/81013/go2html/sample.go | 308 |
diff --git a/homework/81013/README.md b/homework/81013/README.md new file mode 100644 index 0000000..b05d85f --- /dev/null +++ b/homework/81013/README.md @@ -0,0 +1 @@ +Some Homeworks might live here. diff --git a/homework/81013/go2html/CommonTypes.h b/homework/81013/go2html/CommonTypes.h new file mode 100644 index 0000000..f279e34 --- /dev/null +++ b/homework/81013/go2html/CommonTypes.h @@ -0,0 +1,37 @@ +#pragma once +#include <string> +#include <vector> +#include <unordered_map> +#include <memory> + +template<typename T> +using IPLVector = std::vector<T>; +using IPLString = std::string; + +template <typename Key, typename T> +using IPLUnorderedMap = std::unordered_map<Key, T>; + +template <typename T> +using IPLSharedPtr = std::shared_ptr<T>; + +template <typename T, class... Args> +inline IPLSharedPtr<T> IPLMakeSharePtr(Args&&... args) { + return std::make_shared<T, Args...>(args...); +}; + +template< typename T> +using IPLEnableShared = std::enable_shared_from_this<T>; + +struct IPLError +{ + unsigned Row; + unsigned Column; + IPLString File; + IPLString What; +}; + +struct IPLResult +{ + bool IsSuccessful; + IPLError Error; +};
\ No newline at end of file diff --git a/homework/81013/go2html/Highlighter.cpp b/homework/81013/go2html/Highlighter.cpp new file mode 100644 index 0000000..8295610 --- /dev/null +++ b/homework/81013/go2html/Highlighter.cpp @@ -0,0 +1,219 @@ +#include <iostream> +#include <fstream> + +#include "CommonTypes.h" +#include "Lexer.h" +#include "Highlighter.h" + +#define KEYWORD_COLOR "#ad42f4" +#define IDENTIFIER_COLOR "#41c4f4" +#define OPERATOR_COLOR "#f49541" +#define NUMBER_COLOR "#bab103" +#define STRING_COLOR "#ff11bf" +#define RUNE_COLOR "#5abc00" +#define CONST_COLOR "#f94040" +#define COMMENT_COLOR "#0b8700" + +namespace +{ + void PrintToken( + const Token& token, + const IPLString& className, + std::ostream& out) + { + switch (token.Type) + { + case TokenType::Whitespace: + out << token.Lexeme; + break; + case TokenType::Float: + case TokenType::DecimalInteger: + case TokenType::ImaginaryNum: + out << "<span class=\"" << className << "\">" << token.Number << "</span>"; + default: + out << "<span class=\"" << className << "\">" << token.Lexeme << "</span>"; + break; + } + } +} + +IPLResult Highlight(std::ifstream& infile, std::ofstream& out) +{ + out << + "<html>\n" + "<head>\n" + "<style>\n" + ".keyword {\n" + "color: " << KEYWORD_COLOR << ";\n" + "}\n" + "\n" + ".identifier {\n" + "color: " << IDENTIFIER_COLOR << ";\n" + "}\n" + "\n" + ".operator {\n" + "color: " << OPERATOR_COLOR << ";\n" + "}\n" + "\n" + ".number {\n" + "color: " << NUMBER_COLOR << ";\n" + "}\n" + "\n" + ".string {\n" + "color: " << STRING_COLOR << ";\n" + "}\n" + "\n" + ".rune {\n" + "color: " << RUNE_COLOR << ";\n" + "}\n" + "\n" + ".const {\n" + "color: " << CONST_COLOR << ";\n" + "}\n" + "\n" + ".comment {\n" + "color: " << COMMENT_COLOR << ";\n" + "}\n" + "</style>\n" + "</head>\n" + "<body><pre>\n" << std::endl; + + std::string line; + std::vector<Token> tokens; + + while (std::getline(infile, line)) + { + IPLResult result = Tokenize(line.c_str(), tokens, {true, true}); + if (!result.IsSuccessful) + { + return result; + } + + for (auto& token : tokens) + { + switch (token.Type) + { + case TokenType::Break: + case TokenType::Default: + case TokenType::Func: + case TokenType::Interface: + case TokenType::Select: + case TokenType::Case: + case TokenType::Defer: + case TokenType::Go: + case TokenType::Map: + case TokenType::Struct: + case TokenType::Chan: + case TokenType::Else: + case TokenType::Goto: + case TokenType::Package: + case TokenType::Switch: + case TokenType::Const: + case TokenType::Fallthrough: + case TokenType::If: + case TokenType::Range: + case TokenType::Type: + case TokenType::Continue: + case TokenType::For: + case TokenType::Import: + case TokenType::Return: + case TokenType::Var: + + PrintToken(token, "keyword", out); + break; + + case TokenType::Plus: + case TokenType::Minus: + case TokenType::Star: + case TokenType::Division: + case TokenType::Modulo: + + case TokenType::NOT: + case TokenType::BitwiseAND: + case TokenType::BitwiseOR: + case TokenType::BitwiseXOR: + case TokenType::BitClear: + case TokenType::ShiftLeft: + case TokenType::ShiftRight: + + case TokenType::PlusEqual: + case TokenType::MinusEqual: + case TokenType::MultEqual: + case TokenType::DivEqual: + case TokenType::ModuloEqual: + + case TokenType::ConditionalAND: + case TokenType::ConditionalOR: + case TokenType::ReceiveOperator: + case TokenType::PlusPlus: + case TokenType::MinusMinus: + + case TokenType::Equal: + case TokenType::Less: + case TokenType::Greater: + + case TokenType::NOTEqual: + case TokenType::LessEqual: + case TokenType::GreaterEqual: + + case TokenType::Assignment: + case TokenType::ShortAssignment: + + case TokenType::Variadic: + + case TokenType::LeftParen: + case TokenType::RightParen: + case TokenType::LeftSquareBracket: + case TokenType::RightSquareBracket: + case TokenType::LeftBrace: + case TokenType::RightBrace: + + case TokenType::Comma: + case TokenType::Dot: + + case TokenType::Semicolon: + case TokenType::Colon: + + PrintToken(token, "operator", out); + break; + + case TokenType::Float: + case TokenType::DecimalInteger: + case TokenType::ImaginaryNum: + + PrintToken(token, "number", out); + break; + + case TokenType::String: + + PrintToken(token, "string", out); + break; + + case TokenType::True: + case TokenType::False: + case TokenType::Nil: + + PrintToken(token, "const", out); + break; + + case TokenType::Comment: + + PrintToken(token, "comment", out); + break; + + default: + PrintToken(token, "", out); + break; + } + } + + out << std::endl; + tokens.clear(); + } + + out << + "</pre></body>\n" + "</html>" << std::endl; + + return {true, IPLError() }; +}
\ No newline at end of file diff --git a/homework/81013/go2html/Highlighter.h b/homework/81013/go2html/Highlighter.h new file mode 100644 index 0000000..a40f5d0 --- /dev/null +++ b/homework/81013/go2html/Highlighter.h @@ -0,0 +1,8 @@ +#pragma once + +#include <fstream> + +#include "CommonTypes.h" +#include "Lexer.h" + +IPLResult Highlight(std::ifstream& in, std::ofstream& out); diff --git a/homework/81013/go2html/Lexer.cpp b/homework/81013/go2html/Lexer.cpp new file mode 100644 index 0000000..fb53f89 --- /dev/null +++ b/homework/81013/go2html/Lexer.cpp @@ -0,0 +1,668 @@ +#include "Lexer.h" +#include <utility> + +namespace +{ + inline bool IsUpperCase(char c) + { + return c >= 'A' && c <= 'Z'; + } + + inline bool IsLowerCase(char c) + { + return c >= 'a' && c <= 'z'; + } + + inline bool IsDigit(char c) + { + return c >= '0' && c <= '9'; + } + + inline bool IsValidIdentifierStartingChar(char c) + { + return IsUpperCase(c) || IsLowerCase(c) || c == '_'; + } + + inline bool IsValidIdentifierChar(char c) + { + return IsValidIdentifierStartingChar(c) || IsDigit(c); + } + + inline bool IsStringBound(char c) + { + return c == '\'' || c == '"' || c == '`'; + } + + inline bool IsWhiteSpace(char c) + { + return c == ' ' || c == '\t'; + } + + inline bool IsNewLine(char c) + { + return c == '\n'; + } + + inline bool IsEnd(char c) + { + return c == '\0'; + } +} + +struct Keyword +{ + TokenType Type; + IPLString Content; +}; + +using Identifier = Keyword; + +enum class State : unsigned char +{ + Success, + Fail, + Error +}; + +#define RETURN_SUCCESS(ret) m_GenerationState = State::Success; return ret; +#define RETURN_FAIL(ret) m_GenerationState = State::Fail; return ret; +#define RETURN_ERROR(ret) m_GenerationState = State::Error; return ret; + +class Tokenizer +{ +public: + Tokenizer(const char* code, const LexerSettings& settings); + IPLResult Tokenize(IPLVector<Token>& tokens); + +private: + Token NextToken(); + + inline Token ProduceSkipToken(); + inline Token ProduceToken(TokenType type); + inline Token ProduceToken(TokenType type, IPLString lexeme, double number); + inline Token ProduceErrorToken(); + + IPLString ParseComment(); + IPLString ParseWhitespaces(); + double ParseNumber(); + IPLString ParseString(); + Keyword ParseKeyword(); + Identifier ParseIdentifier(); + + inline bool IsStateSuccess() const; + inline bool IsStateError() const; + + void SetError(const IPLString& what); + + bool SkipWhiteSpaces(); + bool SkipNewLine(); + bool Match(const char c); + + inline void NextSymbol(); + inline void PreviousSymbol(); + inline void NextLine(); + + unsigned m_Line; + unsigned m_Column; + unsigned m_Current; + const char* m_Code; + + IPLError m_Error; + State m_GenerationState; + + LexerSettings m_Settings; + bool m_SkipNextToken; + + IPLUnorderedMap<IPLString, TokenType> m_KeyWordsTable; +}; + +IPLResult Tokenize(const char* code, IPLVector<Token>& tokens, const LexerSettings& settings) +{ + Tokenizer tokenizer(code, settings); + + return tokenizer.Tokenize(tokens); +} + +IPLResult Tokenize(const char* code, IPLVector<Token>& tokens) +{ + return Tokenize(code, tokens, { false, false }); +} + +Tokenizer::Tokenizer(const char* code, const LexerSettings& settings) + : m_Line(0) + , m_Column(0) + , m_Current(0) + , m_Code(code) + , m_Error() + , m_GenerationState(State::Success) + , m_Settings(settings) + , m_SkipNextToken(false) +{ + m_KeyWordsTable["break"] = TokenType::Break; + m_KeyWordsTable["default"] = TokenType::Default; + m_KeyWordsTable["func"] = TokenType::Func; + m_KeyWordsTable["interface"] = TokenType::Interface; + m_KeyWordsTable["select"] = TokenType::Select; + m_KeyWordsTable["case"] = TokenType::Case; + m_KeyWordsTable["defer"] = TokenType::Defer; + m_KeyWordsTable["go"] = TokenType::Go; + m_KeyWordsTable["map"] = TokenType::Map; + m_KeyWordsTable["struct"] = TokenType::Struct; + m_KeyWordsTable["chan"] = TokenType::Chan; + m_KeyWordsTable["else"] = TokenType::Else; + m_KeyWordsTable["goto"] = TokenType::Goto; + m_KeyWordsTable["package"] = TokenType::Package; + m_KeyWordsTable["switch"] = TokenType::Switch; + m_KeyWordsTable["const"] = TokenType::Const; + m_KeyWordsTable["fallthrough"] = TokenType::Fallthrough; + m_KeyWordsTable["if"] = TokenType::If; + m_KeyWordsTable["range"] = TokenType::Range; + m_KeyWordsTable["type"] = TokenType::Type; + m_KeyWordsTable["continue"] = TokenType::Continue; + m_KeyWordsTable["for"] = TokenType::For; + m_KeyWordsTable["import"] = TokenType::Import; + m_KeyWordsTable["return"] = TokenType::Return; + m_KeyWordsTable["var"] = TokenType::Var; + + m_KeyWordsTable["true"] = TokenType::True; + m_KeyWordsTable["false"] = TokenType::False; + + m_KeyWordsTable["nil"] = TokenType::Nil; +} + +IPLResult Tokenizer::Tokenize(IPLVector<Token>& tokens) +{ + do + { + Token token; + do + { + token = NextToken(); + } while (m_SkipNextToken); + + if (m_GenerationState == State::Error) + { + return IPLResult{ false, IPLError(m_Error) }; + } + + tokens.emplace_back(token); + } while (tokens.back().Type != TokenType::Eof); + + return IPLResult{ true, IPLError() }; +} + +bool Tokenizer::SkipWhiteSpaces() +{ + const auto old = m_Current; + while (IsWhiteSpace(m_Code[m_Current])) + { + NextSymbol(); + } + return old != m_Current; +} + +bool Tokenizer::SkipNewLine() +{ + const auto old = m_Current; + while (IsNewLine(m_Code[m_Current])) + { + NextLine(); + } + return old != m_Current; +} + +bool Tokenizer::Match(const char c) +{ + if (c == m_Code[m_Current]) + { + if (!IsNewLine(m_Code[m_Current])) + { + NextSymbol(); + } + else + { + NextLine(); + } + + return true; + } + return false; +} + +inline void Tokenizer::NextSymbol() +{ + ++m_Current; + ++m_Column; +} + +inline void Tokenizer::PreviousSymbol() +{ + --m_Current; + --m_Column; +} + +inline void Tokenizer::NextLine() +{ + ++m_Current; + ++m_Line; + m_Column = 0; +} + +inline Token Tokenizer::ProduceToken(TokenType type, IPLString lexeme, double number = 0.0) +{ + return Token{ type, m_Line, std::move(lexeme), number }; +} + +inline Token Tokenizer::ProduceSkipToken() +{ + return ProduceToken(TokenType::Invalid, ""); +} + +inline Token Tokenizer::ProduceToken(TokenType type) +{ + switch (type) + { + case TokenType::Plus: + return ProduceToken(type, "+"); + case TokenType::Minus: + return ProduceToken(type, "-"); + case TokenType::Star: + return ProduceToken(type, "*"); + case TokenType::Division: + return ProduceToken(type, "/"); + case TokenType::Modulo: + return ProduceToken(type, "%"); + + case TokenType::PlusEqual: + return ProduceToken(type, "+="); + case TokenType::MinusEqual: + return ProduceToken(type, "-="); + case TokenType::MultEqual: + return ProduceToken(type, "*="); + case TokenType::DivEqual: + return ProduceToken(type, "/="); + case TokenType::ModuloEqual: + return ProduceToken(type, "%="); + + case TokenType::NOT: + return ProduceToken(type, "!"); + case TokenType::BitwiseAND: + return ProduceToken(type, "&"); + case TokenType::BitwiseOR: + return ProduceToken(type, "|"); + case TokenType::BitwiseXOR: + return ProduceToken(type, "^"); + case TokenType::BitClear: + return ProduceToken(type, "^&"); + case TokenType::ShiftLeft: + return ProduceToken(type, "<<"); + case TokenType::ShiftRight: + return ProduceToken(type, ">>"); + + case TokenType::ConditionalAND: + return ProduceToken(type, "&&"); + case TokenType::ConditionalOR: + return ProduceToken(type, "||"); + + case TokenType::ReceiveOperator: + return ProduceToken(type, "<-"); + + case TokenType::PlusPlus: + return ProduceToken(type, "++"); + case TokenType::MinusMinus: + return ProduceToken(type, "--"); + + case TokenType::Equal: + return ProduceToken(type, "=="); + case TokenType::Less: + return ProduceToken(type, "<"); + case TokenType::Greater: + return ProduceToken(type, ">"); + + case TokenType::NOTEqual: + return ProduceToken(type, "!="); + case TokenType::LessEqual: + return ProduceToken(type, "<="); + case TokenType::GreaterEqual: + return ProduceToken(type, ">="); + + case TokenType::Assignment: + return ProduceToken(type, "="); + case TokenType::ShortAssignment: + return ProduceToken(type, ":="); + + case TokenType::Variadic: + return ProduceToken(type, "..."); + + case TokenType::LeftBrace: + return ProduceToken(type, "{"); + case TokenType::RightBrace: + return ProduceToken(type, "}"); + case TokenType::RightSquareBracket: + return ProduceToken(type, "]"); + case TokenType::LeftSquareBracket: + return ProduceToken(type, "["); + case TokenType::LeftParen: + return ProduceToken(type, "("); + case TokenType::RightParen: + return ProduceToken(type, ")"); + + case TokenType::Comma: + return ProduceToken(type, ","); + case TokenType::Dot: + return ProduceToken(type, "."); + + default: // EoF Invalid + break; + } + return ProduceToken(type, ""); +} + +inline Token Tokenizer::ProduceErrorToken() +{ + return ProduceToken(TokenType::Invalid, ""); +} + +IPLString Tokenizer::ParseComment() +{ + auto start = m_Current; + + if (!Match('/')) + { + RETURN_FAIL(IPLString()); + } + + if (Match('/')) + { + while (!IsEnd(m_Code[m_Current]) && !IsNewLine(m_Code[m_Current])) + { + NextSymbol(); + } + RETURN_SUCCESS(IPLString(m_Code + start, m_Code + m_Current)) + } + else if (!Match('*')) + { + PreviousSymbol(); + RETURN_FAIL(IPLString()); + } + + while (!IsEnd(m_Code[m_Current])) + { + if (Match('*') && Match('/')) + { + RETURN_SUCCESS(IPLString(m_Code + start, m_Code + m_Current)); + } + NextSymbol(); + } + + SetError("unterminated comment"); + RETURN_ERROR(IPLString()); +} + +IPLString Tokenizer::ParseWhitespaces() +{ + if (!IsWhiteSpace(m_Code[m_Current]) && !IsNewLine(m_Code[m_Current])) + { + RETURN_FAIL(IPLString()); + } + + auto start = m_Current; + while (true) + { + if (IsWhiteSpace(m_Code[m_Current])) + { + NextSymbol(); + } + else if (IsNewLine(m_Code[m_Current])) + { + NextLine(); + } + else + { + break; + } + } + + RETURN_SUCCESS(IPLString(m_Code + start, m_Code + m_Current)); +} + +// todo - many Go numbers, much refactoring +double Tokenizer::ParseNumber() +{ + if (!IsDigit(m_Code[m_Current])) + { + RETURN_FAIL(0.0); + } + + size_t parsedBytes = 0; + double number = std::stod(m_Code + m_Current, &parsedBytes); + m_Current += static_cast<unsigned>(parsedBytes); + m_Column += static_cast<unsigned>(parsedBytes); + + RETURN_SUCCESS(number); +} + +IPLString Tokenizer::ParseString() +{ + if (!IsStringBound(m_Code[m_Current])) + { + RETURN_FAIL(IPLString()); + } + + char bound = m_Code[m_Current]; + auto start = m_Current; + + // skip first " or ', or ` + NextSymbol(); + while (m_Code[m_Current] != bound && !IsEnd(m_Code[m_Current]) && !IsNewLine(m_Code[m_Current])) + { + NextSymbol(); + } + + if (IsEnd(m_Code[m_Current]) || IsNewLine(m_Code[m_Current])) + { + SetError("\"\" string literal contains an unescaped line break"); + RETURN_ERROR(IPLString()); + } + + // skip second " or ', or ` + NextSymbol(); + + RETURN_SUCCESS(IPLString(m_Code + start, m_Code + m_Current)); +} + +Keyword Tokenizer::ParseKeyword() +{ + if (!IsLowerCase(m_Code[m_Current])) + { + RETURN_FAIL(Keyword()); + } + + auto start = m_Current; + + NextSymbol(); + while (IsLowerCase(m_Code[m_Current])) + { + NextSymbol(); + } + + auto key = IPLString(m_Code + start, m_Code + m_Current); + auto keyword = m_KeyWordsTable.find(key); + + if (keyword == m_KeyWordsTable.end()) + { + // It's not a keyword so we must revert current counter + m_Column = m_Column + start - m_Current; + m_Current = start; + + RETURN_FAIL(Keyword()); + } + + RETURN_SUCCESS((Keyword{ keyword->second, keyword->first })); +} + +Identifier Tokenizer::ParseIdentifier() +{ + if (!IsValidIdentifierStartingChar(m_Code[m_Current])) + { + RETURN_FAIL(Identifier()); + } + + auto start = m_Current; + + NextSymbol(); + while (IsValidIdentifierChar(m_Code[m_Current])) + { + NextSymbol(); + } + + RETURN_SUCCESS((Identifier{ TokenType::Identifier, IPLString(m_Code + start, m_Code + m_Current) })); +} + +inline bool Tokenizer::IsStateSuccess() const +{ + return m_GenerationState == State::Success; +} + +inline bool Tokenizer::IsStateError() const +{ + return m_GenerationState == State::Error; +} + +void Tokenizer::SetError(const IPLString& what) +{ + m_Error = IPLError{ m_Line, m_Column, "", "Syntax error: " + what }; +} + +Token Tokenizer::NextToken() +{ + m_SkipNextToken = false; + + if (IsEnd(m_Code[m_Current])) + { + return ProduceToken(TokenType::Eof); + } + + const auto& comment = ParseComment(); + if (IsStateError()) + { + return ProduceErrorToken(); + } + if (IsStateSuccess()) + { + if (!m_Settings.CreateCommentTokens) + { + m_SkipNextToken = true; + return ProduceSkipToken(); + } + return ProduceToken(TokenType::Comment, comment); + } + + const auto& spaces = ParseWhitespaces(); + if (IsStateSuccess()) + { + if (!m_Settings.CreateWhitespaceTokens) + { + m_SkipNextToken = true; + return ProduceSkipToken(); + } + return ProduceToken(TokenType::Whitespace, spaces); + } + + // Single Char + switch (m_Code[m_Current]) + { + case '-': NextSymbol(); return Match('-') ? ProduceToken(TokenType::MinusMinus) : + Match('=') ? ProduceToken(TokenType::MinusEqual) : + ProduceToken(TokenType::Minus); + case '+': NextSymbol(); return Match('+') ? ProduceToken(TokenType::PlusPlus) : + Match('=') ? ProduceToken(TokenType::PlusEqual) : + ProduceToken(TokenType::Plus); + + case '*': NextSymbol(); return Match('=') ? ProduceToken(TokenType::MultEqual) : + ProduceToken(TokenType::Star); + case '/': NextSymbol(); return Match('=') ? ProduceToken(TokenType::DivEqual) : + ProduceToken(TokenType::Division); + case '%': NextSymbol(); return Match('=') ? ProduceToken(TokenType::ModuloEqual) : + ProduceToken(TokenType::Modulo); + + case '&': NextSymbol(); return Match('&') ? ProduceToken(TokenType::ConditionalAND) : + ProduceToken(TokenType::BitwiseAND); + + case '|': NextSymbol(); return Match('|') ? ProduceToken(TokenType::ConditionalOR) : + ProduceToken(TokenType::BitwiseOR); + + case '^': NextSymbol(); return Match('&') ? ProduceToken(TokenType::BitClear) : + ProduceToken(TokenType::BitwiseXOR); + + case '=': NextSymbol(); return Match('=') ? ProduceToken(TokenType::Equal) : + ProduceToken(TokenType::Assignment); + + case '>': NextSymbol(); return Match('=') ? ProduceToken(TokenType::GreaterEqual) : + Match('>') ? ProduceToken(TokenType::ShiftRight) : + ProduceToken(TokenType::Greater); + + case '<': NextSymbol(); return Match('=') ? ProduceToken(TokenType::LessEqual) : + Match('<') ? ProduceToken(TokenType::ShiftLeft) : + Match('-') ? ProduceToken(TokenType::ReceiveOperator) : + ProduceToken(TokenType::Less); + + case '!': NextSymbol(); return Match('=') ? ProduceToken(TokenType::NOTEqual) : ProduceToken(TokenType::NOT); + + case '(': NextSymbol(); return ProduceToken(TokenType::LeftParen); + case ')': NextSymbol(); return ProduceToken(TokenType::RightParen); + case '[': NextSymbol(); return ProduceToken(TokenType::LeftSquareBracket); + case ']': NextSymbol(); return ProduceToken(TokenType::RightSquareBracket); + case '{': NextSymbol(); return ProduceToken(TokenType::LeftBrace); + case '}': NextSymbol(); return ProduceToken(TokenType::RightBrace); + + case ',': NextSymbol(); return ProduceToken(TokenType::Comma); + case '.': NextSymbol(); return Match('.') ? Match('.') ? ProduceToken(TokenType::Variadic) : + ProduceToken(TokenType::Invalid, "") : + ProduceToken(TokenType::Dot); + + case ';': NextSymbol(); return ProduceToken(TokenType::Semicolon); + case ':': NextSymbol(); return Match('=') ? ProduceToken(TokenType::ShortAssignment) : + ProduceToken(TokenType::Colon); + + // todo: see if I've left some go-specific single chars out + + default: + break; + } + + const auto& number = ParseNumber(); + if (IsStateSuccess()) + { + // todo - add integers and perhaps imaginary numbers + return ProduceToken(TokenType::Float, "", number); + } + + const auto& string = ParseString(); + if (IsStateSuccess()) + { + if (string.length() != 1) + { + return ProduceToken(TokenType::String, string); + } + return ProduceToken(TokenType::Rune, string); + } + else if (IsStateError()) + { + return ProduceErrorToken(); + } + + const auto& keyword = ParseKeyword(); + if (IsStateSuccess()) + { + return ProduceToken(keyword.Type, keyword.Content); + } + + const auto& identifier = ParseIdentifier(); + if (IsStateSuccess()) + { + return ProduceToken(identifier.Type, identifier.Content); + } + + return ProduceErrorToken(); +} diff --git a/homework/81013/go2html/Lexer.h b/homework/81013/go2html/Lexer.h new file mode 100644 index 0000000..d1b5ae6 --- /dev/null +++ b/homework/81013/go2html/Lexer.h @@ -0,0 +1,125 @@ +#pragma once + +#include "CommonTypes.h" + +enum class TokenType +{ + // Keywords + Break, + Default, + Func, + Interface, + Select, + Case, + Defer, + Go, + Map, + Struct, + Chan, + Else, + Goto, + Package, + Switch, + Const, + Fallthrough, + If, + Range, + Type, + Continue, + For, + Import, + Return, + Var, + + // Operators & Punctuation + Plus, + Minus, + Star, + Division, + Modulo, + + NOT, + BitwiseAND, + BitwiseOR, + BitwiseXOR, + BitClear, + ShiftLeft, + ShiftRight, + + PlusEqual, + MinusEqual, + MultEqual, + DivEqual, + ModuloEqual, + + // -> skipped bitwise + equal stuff + + ConditionalAND, + ConditionalOR, + ReceiveOperator, + PlusPlus, + MinusMinus, + + Equal, + Less, + Greater, + + NOTEqual, + LessEqual, + GreaterEqual, + + Assignment, + ShortAssignment, + + Variadic, + + LeftParen, + RightParen, + LeftSquareBracket, + RightSquareBracket, + LeftBrace, + RightBrace, + + Comma, + Dot, + + Semicolon, + Colon, + + // Identifier + Identifier, + + // Types + DecimalInteger, // not really supported ATM + Float, + ImaginaryNum, // not really supported ATM + String, + Rune, + + True, + False, + Nil, + + Whitespace, + Comment, + + Eof, + Invalid +}; + +struct Token +{ + TokenType Type; + unsigned Line; + IPLString Lexeme; + double Number; +}; + +struct LexerSettings +{ + bool CreateWhitespaceTokens; + bool CreateCommentTokens; +}; + +IPLResult Tokenize(const char* code, IPLVector<Token>& tokens, const LexerSettings& settings); +IPLResult Tokenize(const char* code, IPLVector<Token>& tokens);
\ No newline at end of file diff --git a/homework/81013/go2html/README.md b/homework/81013/go2html/README.md new file mode 100644 index 0000000..10e233f --- /dev/null +++ b/homework/81013/go2html/README.md @@ -0,0 +1,8 @@ +Usage: + +Make solution via premake5 +> premake5 vs2015 premake5.lua + +Open the vs solution, add two command line arguments - the go file that you want to highlight and the html in which you would like to write + +example arguments: sample.go res.html diff --git a/homework/81013/go2html/main.cpp b/homework/81013/go2html/main.cpp new file mode 100644 index 0000000..4a8d65a --- /dev/null +++ b/homework/81013/go2html/main.cpp @@ -0,0 +1,19 @@ +#include <iostream> +#include <string> +#include <fstream> +#include <sstream> + +#include "CommonTypes.h" +#include "Lexer.h" +#include "Highlighter.h" + +int main(int argc, char **argv) +{ + // todo: WRITE PROPER TESTS + std::ifstream infile(argv[1]); + std::ofstream offile(argv[2]); + + Highlight(infile, offile); + + return 0; +} diff --git a/homework/81013/go2html/premake5.lua b/homework/81013/go2html/premake5.lua new file mode 100644 index 0000000..17131f7 --- /dev/null +++ b/homework/81013/go2html/premake5.lua @@ -0,0 +1,22 @@ +solution 'HL' + configurations {'debug', 'release'} + flags { + 'FatalWarnings' + } + + local root = 'build/' + + configuration 'debug' + targetdir(root .. 'bin/debug') + objdir(root .. 'obj/debug') + + configuration 'release' + targetdir(root .. 'bin/release') + objdir(root .. 'obj/release') + configuration '*' + + project 'HL' + kind 'ConsoleApp' + language 'C++' + files '*.cpp' + files '*.h' diff --git a/homework/81013/go2html/sample.go b/homework/81013/go2html/sample.go new file mode 100644 index 0000000..9c76ea2 --- /dev/null +++ b/homework/81013/go2html/sample.go @@ -0,0 +1,308 @@ +package main + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "net/url" + "regexp" + "strconv" + "sync" + //"fmt" +) + +type Cell struct { + X int64 `json:"x"` + Y int64 `json:"y"` +} + +type GameOfLifeHandler struct { + Generation int + Board map[Cell]bool + Neighbours map[Cell]int + RespWriter http.ResponseWriter + BoardMutex sync.Mutex + NeighboursMutex sync.Mutex + RequestMutex sync.RWMutex +} + +/* util functions */ +func (g *GameOfLifeHandler) sendError(err error) { + g.RespWriter.Header().Set("Error", err.Error()) + g.RespWriter.WriteHeader(http.StatusBadRequest) +} + +func (g *GameOfLifeHandler) parseXYQuery(r *http.Request) (result Cell, err error) { + values, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + g.sendError(err) + return + } + + // validate + if len(values) != 2 { + err = errors.New("Invalid parameters query - invalid number of query parameters") + g.sendError(err) + return + } + + xString := values.Get("x") + yString := values.Get("y") + + if xString == "" || yString == "" { + err = errors.New("Invalid parameters query - x or y not set") + g.sendError(err) + return + } + + x, err := strconv.ParseInt(xString, 10, 64) + if err != nil { + g.sendError(err) + return + } + y, err := strconv.ParseInt(yString, 10, 64) + if err != nil { + g.sendError(err) + return + } + + result.X = x + result.Y = y + + return +} + +func updateNeighbours(cell Cell, neighbours *map[Cell]int) { + var neighbour Cell + + for i := cell.X - 1; i <= cell.X+1; i++ { + for j := cell.Y - 1; j <= cell.Y+1; j++ { + + if i == cell.X && j == cell.Y { + continue + } + + neighbour.X = i + neighbour.Y = j + + (*neighbours)[neighbour]++ + } + } +} + +/* url handlers */ +func (g *GameOfLifeHandler) addNewCells(r *http.Request) (err error) { + var cellSlice []Cell + cellSlice = make([]Cell, 0, 2) + + jsonBytes, err := ioutil.ReadAll(r.Body) + if err != nil { + g.sendError(err) + return + } + + err = json.Unmarshal(jsonBytes, &cellSlice) + if err != nil { + g.sendError(err) + return + } + + for _, cell := range cellSlice { + g.BoardMutex.Lock() + g.Board[cell] = true + g.BoardMutex.Unlock() + + g.NeighboursMutex.Lock() + updateNeighbours(cell, &g.Neighbours) + g.NeighboursMutex.Unlock() + } + + return err +} + +// writes json as a response +func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell Cell) (err error) { + alive := make(map[string]bool) + alive["alive"] = g.Board[cell] + + output, err := json.Marshal(alive) + if err != nil { + g.sendError(err) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(output) + return +} + +// writes json as a response +func (g *GameOfLifeHandler) generation(w http.ResponseWriter) { + generation := make([]Cell, 0, len(g.Board)) + + for cell := range g.Board { + generation = append(generation, cell) + } + + currentGeneration := make(map[string]interface{}) + currentGeneration["generation"] = strconv.Itoa(g.Generation) + currentGeneration["living"] = generation + + output, err := json.Marshal(currentGeneration) + if err != nil { + g.sendError(err) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(output) +} + +func (g *GameOfLifeHandler) evolve(w http.ResponseWriter) { + newBoard := make(map[Cell]bool) + newNeighbourBoard := make(map[Cell]int) + + for cell, neighbours := range g.Neighbours { + if g.Board[cell] { + if neighbours == 2 || neighbours == 3 { + newBoard[cell] = true + + updateNeighbours(cell, &newNeighbourBoard) + } + } else { + if neighbours == 3 { + newBoard[cell] = true + + updateNeighbours(cell, &newNeighbourBoard) + } + } + } + + g.BoardMutex.Lock() + g.Board = newBoard + g.BoardMutex.Unlock() + + g.NeighboursMutex.Lock() + g.Neighbours = newNeighbourBoard + g.NeighboursMutex.Unlock() + + g.Generation++ + + w.WriteHeader(http.StatusNoContent) +} + +func (g *GameOfLifeHandler) reset(w http.ResponseWriter) { + g.Generation = 0 + + g.BoardMutex.Lock() + g.Board = make(map[Cell]bool) + g.BoardMutex.Unlock() + + w.WriteHeader(http.StatusNoContent) +} + +func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + + switch { + case regexp.MustCompile(`/generation/evolve/`).MatchString(path): + // POST + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + } else { + g.RequestMutex.Lock() + + g.evolve(w) + + g.RequestMutex.Unlock() + } + + case regexp.MustCompile(`/generation/`).MatchString(path): + // GET + if r.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + } else { + g.RequestMutex.RLock() + + g.generation(w) + + g.RequestMutex.RUnlock() + } + + case regexp.MustCompile(`/cell/status/`).MatchString(path): + // GET + if r.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + } else { + g.RequestMutex.RLock() + + cell, err := g.parseXYQuery(r) + if err == nil { + g.checkCell(w, cell) + } + + g.RequestMutex.RUnlock() + } + + case regexp.MustCompile(`/cells/`).MatchString(path): + // POST + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + } else { + g.RequestMutex.Lock() + + if err := g.addNewCells(r); err == nil { + w.WriteHeader(http.StatusCreated) + } + + g.RequestMutex.Unlock() + } + + case regexp.MustCompile(`/reset/`).MatchString(path): + // POST + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + } else { + g.RequestMutex.Lock() + + g.reset(w) + + g.RequestMutex.Unlock() + } + + default: + w.WriteHeader(http.StatusNotFound) + } +} + +func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler { + board := make(map[Cell]bool) + neighbours := make(map[Cell]int) + + var newCell Cell + + for _, cell := range cells { + newCell.X = cell[0] + newCell.Y = cell[1] + + board[newCell] = true + + updateNeighbours(newCell, &neighbours) + } + + gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours} + + return gameOfLifeHandler +} + +func main() { + gofh := NewGameOfLifeHandler([][2]int64{ + {2, 3}, + {2, 4}, + {-20, 4}, + {-42, 0}, + }) + + http.ListenAndServe(":8080", gofh) +} |
