commit 2999c295aff2ea7af21e6c931a6f53efa2873aa1 Author: hasslesstech Date: Fri Oct 2 20:31:24 2026 +0300 initial commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d378480 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10) + +SET(CMAKE_CXX_FLAGS_DEBUG "-g") + +project(pzks) + +set (GLOBAL_INC PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/inc + ${CMAKE_CURRENT_SOURCE_DIR} +) + + +add_library(parser STATIC) + +target_sources(parser PRIVATE + src/parser.cpp +) + +target_include_directories(parser PRIVATE + ${GLOBAL_INC} +) + + +add_executable(tester) + +target_sources(tester PRIVATE + src/test.cpp +) + +target_include_directories(tester PRIVATE + ${GLOBAL_INC} + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(tester PRIVATE + parser +) diff --git a/config.h b/config.h new file mode 100644 index 0000000..30759e6 --- /dev/null +++ b/config.h @@ -0,0 +1,3 @@ +#pragma once + +#define DEBUG 1 diff --git a/inc/expression.hpp b/inc/expression.hpp new file mode 100644 index 0000000..11247a2 --- /dev/null +++ b/inc/expression.hpp @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include + +namespace Expression { + +#if 0 +namespace TokenType { + +enum TokenType { + VAR, + CONST, + OPERATOR +}; + +} +#endif + +class Token { +public: + enum TokenType { + VAR, + CONST, + OPERATOR + } t; + + enum Operator { + OPA, // opening parentheses + CPA, // closing parentheses + ADD, + SUB, + MUL, + DIV, + POW + }; + + union TokenValue { + std::shared_ptr s; + double d; + Operator o; + + explicit TokenValue(std::string& s) + : s(std::make_shared(s)) + { + } + + explicit TokenValue(double d) + : d(d) + { + } + + explicit TokenValue(enum Operator o) + : o(o) + { + } + + explicit TokenValue(void) + { + } + + ~TokenValue(void) + { + } + } v; + + Token(std::string s) + : v(s) + { + t = VAR; + } + + Token(double d) + : v(d) + { + t = CONST; + } + + Token(enum Operator o) + : v(o) + { + t = OPERATOR; + } + + Token(Expression::Token&& other) + { + t = other.t; + + switch (t) + { + case VAR: + v.s = other.v.s; + break; + case CONST: + v.d = other.v.d; + break; + case OPERATOR: + v.o = other.v.o; + break; + default: + std::cout << "Warning: token type is invalid\n"; + break; + } + } + + const char *type_str(void) + { + switch (t) + { + case VAR: + return "VAR"; + case CONST: + return "CONST"; + case OPERATOR: + return "OPERATOR"; + default: + return "Not a valid type"; + } + } + + const char *op_str(void) + { + if (t != OPERATOR) + { + return "Token is not an operator!"; + } + + switch (v.o) + { + case OPA: + return "OPA"; + case CPA: + return "CPA"; + case ADD: + return "ADD"; + case SUB: + return "SUB"; + case MUL: + return "MUL"; + case DIV: + return "DIV"; + case POW: + return "POW"; + default: + return "Invalid operator"; + } + } +}; + +class TreeNode { + std::shared_ptr l; // left hand + std::shared_ptr o; // operation + std::shared_ptr r; // right hand +}; + +} diff --git a/inc/parser.hpp b/inc/parser.hpp new file mode 100644 index 0000000..45755d4 --- /dev/null +++ b/inc/parser.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include "expression.hpp" + +namespace Parser { + +std::shared_ptr> tokenize_input(char *input_string); +int assert_same(std::vector& a, std::vector& b); + +} diff --git a/inc/print.h b/inc/print.h new file mode 100644 index 0000000..95725d8 --- /dev/null +++ b/inc/print.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "config.h" + +#if DEBUG == 1 +#define pr_debug(...) std::cout << "[" << __func__ << ":" << __LINE__ << "] debug: " << __VA_ARGS__ << "\n" +#else +#define pr_debug(...) do {} while (0) +#endif + +#define pr_warn(...) std::cout << "[" << __func__ << ":" << __LINE__ << "] warning: " << __VA_ARGS__ << "\n" diff --git a/inc/test-tools.hpp b/inc/test-tools.hpp new file mode 100644 index 0000000..7b0badf --- /dev/null +++ b/inc/test-tools.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +typedef int (*TestFunc)(void); + +extern std::vector test_funcs; + +#define CHECK(a) do { \ + if (a) { \ + std::cout << "Failed check in " << __func__ << ":" << __LINE__ << "\n" \ + << #a << " does not hold\n"; \ + return 1; \ + } \ +} while (0) + +#define TEST(name) static int name(void) +#define REGISTER(name) test_funcs.push_back(name) diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 0000000..3eadbf9 --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,312 @@ +#include +#include +#include +#include +#include +#include "expression.hpp" +#include "print.h" + +namespace Parser { + +class Parser +{ + enum State { + I, // idle + RC, // read constant + RV, // read variable + OP, // read operator + } s; + + const char *state_str(enum State e) + { + switch (e) + { + case I: + return "I"; + case RC: + return "RC"; + case RV: + return "RV"; + case OP: + return "OP"; + default: + return "Not a valid state!"; + } + } + + void switch_state(enum State e) + { + pr_debug("Leaving " << state_str(s) << ", entering " << state_str(e)); + switch (s) + { + case I: + leave_i(); + break; + case RC: + leave_rc(); + break; + case RV: + leave_rv(); + break; + case OP: + leave_op(); + break; + default: + pr_warn("Warning: broken state machine on leave"); + break; + } + + switch (e) + { + case I: + enter_i(); + break; + case RC: + enter_rc(); + break; + case RV: + enter_rv(); + break; + case OP: + enter_op(); + break; + default: + pr_warn("Warning: broken state machine on enter"); + break; + } + + s = e; + } + +public: + char buffer[256]; + std::shared_ptr>& pv; + + Parser(std::shared_ptr>& pv) + : s(I), pv(pv) + { + buf_clear(); + } + + void buf_clear(void) + { + memset(buffer, 0, 256); + } + + void buf_push(char c) + { + buffer[strlen(buffer)] = c; + } + + int dispatch_to_current(char c) + { + switch (s) + { + case I: + return parse_i(c); + case RC: + return parse_rc(c); + case RV: + return parse_rv(c); + case OP: + return parse_op(c); + default: + pr_warn("State machine is broken, unable to dispatch character " << c << ", returning 0"); + return 0; + } + } + + void enter_i(void) {} + int parse_i(char c) + { + if (isdigit(c) || c == '.') + { + switch_state(RC); + return 1; + } + else if (isalpha(c)) + { + switch_state(RV); + return 1; + } + else if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')') + { + switch_state(OP); + return 1; + } + else + { + pr_warn("Unknown character skipped (" << c << "), still in idle"); + return 0; + } + } + void leave_i(void) {} + + void enter_rc(void) + { + buf_clear(); + } + int parse_rc(char c) + { + if (!(isdigit(c) || c == '.')) + { + switch_state(I); + return 1; + } + else + { + buf_push(c); + return 0; + } + } + void leave_rc(void) + { + double value; + + if (!sscanf(buffer, "%lf", &value)) + { + pr_warn("Invalid CONST: " << buffer); + } + else + { + pv->push_back(Expression::Token(value)); + } + } + + void enter_rv(void) + { + buf_clear(); + } + int parse_rv(char c) + { + if (!isalpha(c)) + { + switch_state(I); + return 1; + } + else + { + buf_push(c); + return 0; + } + } + void leave_rv(void) + { + pv->push_back(Expression::Token(buffer)); + } + + void enter_op(void) + { + buf_clear(); + } + int parse_op(char c) + { + if (!(c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')')) + { + switch_state(I); + return 1; + } + else + { + buf_push(c); + return 0; + } + } + void leave_op(void) + { + if (!strcmp(buffer, "(")) + { + pv->push_back(Expression::Token(Expression::Token::OPA)); + } + else if (!strcmp(buffer, ")")) + { + pv->push_back(Expression::Token(Expression::Token::CPA)); + } + else if (!strcmp(buffer, "+")) + { + pv->push_back(Expression::Token(Expression::Token::ADD)); + } + else if (!strcmp(buffer, "-")) + { + pv->push_back(Expression::Token(Expression::Token::SUB)); + } + else if (!strcmp(buffer, "*")) + { + pv->push_back(Expression::Token(Expression::Token::MUL)); + } + else if (!strcmp(buffer, "/")) + { + pv->push_back(Expression::Token(Expression::Token::DIV)); + } + else if (!strcmp(buffer, "**")) + { + pv->push_back(Expression::Token(Expression::Token::POW)); + } + else + { + pr_warn("Skip unknown operator: " << buffer); + } + } +}; + +std::shared_ptr> tokenize_input(char *input_string) +{ + std::shared_ptr> pv = std::make_shared>(); + + Parser p(pv); + for (int i = 0, l = strlen(input_string); i < l; i++) + { + i -= p.dispatch_to_current(input_string[i]); + } + + (void) p.dispatch_to_current('\0'); + + return pv; +} + +int assert_same(std::vector& a, std::vector& b) +{ + if (a.size() != b.size()) + { + std::cout << "Vectors are of different lengths: " << a.size() << " != " << b.size() << "\n"; + return 1; + } + + for (int i = 0, l = a.size(); i < l; i++) + { + if (a[i].t != b[i].t) + { + std::cout << "Token " << i << " types differ: " << a[i].type_str() << " != " << b[i].type_str() << "\n"; + return 1; + } + + switch (a[i].t) + { + case Expression::Token::VAR: + if (strcmp((*(a[i].v.s)).c_str(), + (*(b[i].v.s)).c_str())) + { + std::cout << "Variable names at " << i << " differ: " << (*(a[i].v.s)).c_str() << " != " << (*(b[i].v.s)).c_str() << "\n"; + return 1; + } + break; + case Expression::Token::CONST: + if (a[i].v.d != b[i].v.d) + { + std::cout << "Constants at " << i << " differ: " << a[i].v.d << " != " << b[i].v.d << "\n"; + return 1; + } + break; + case Expression::Token::OPERATOR: + if (a[i].v.o != b[i].v.o) + { + std::cout << "Operators at " << i << " differ: " << a[i].op_str() << " != " << b[i].op_str() << "\n"; + return 1; + } + break; + } + } + return 0; +} + +} diff --git a/src/test-cases.cpp b/src/test-cases.cpp new file mode 100644 index 0000000..08e7db5 --- /dev/null +++ b/src/test-cases.cpp @@ -0,0 +1,55 @@ +#include "test-tools.hpp" +#include "parser.hpp" +#include "expression.hpp" + +TEST(parser_proper_test_1) +{ + auto o = Parser::tokenize_input((char *) "(1+2-c)"); + std::vector r; + + r.push_back(Expression::Token(Expression::Token::OPA)); + r.push_back(Expression::Token(1)); + r.push_back(Expression::Token(Expression::Token::ADD)); + r.push_back(Expression::Token(2)); + r.push_back(Expression::Token(Expression::Token::SUB)); + r.push_back(Expression::Token("c")); + r.push_back(Expression::Token(Expression::Token::CPA)); + + CHECK(Parser::assert_same(*o, r)); + + return 0; +} + +TEST(parser_proper_test_2) +{ + auto o = Parser::tokenize_input((char *) "(1**5,-snake+marble*34*(35 / 63.2))"); + std::vector r; + + r.push_back(Expression::Token(Expression::Token::OPA)); + r.push_back(Expression::Token(1)); + r.push_back(Expression::Token(Expression::Token::POW)); + r.push_back(Expression::Token(5)); + r.push_back(Expression::Token(Expression::Token::SUB)); + r.push_back(Expression::Token("snake")); + r.push_back(Expression::Token(Expression::Token::ADD)); + r.push_back(Expression::Token("marble")); + r.push_back(Expression::Token(Expression::Token::MUL)); + r.push_back(Expression::Token(34)); + r.push_back(Expression::Token(Expression::Token::MUL)); + r.push_back(Expression::Token(Expression::Token::OPA)); + r.push_back(Expression::Token(35)); + r.push_back(Expression::Token(Expression::Token::DIV)); + r.push_back(Expression::Token(63.2)); + r.push_back(Expression::Token(Expression::Token::CPA)); + r.push_back(Expression::Token(Expression::Token::CPA)); + + CHECK(Parser::assert_same(*o, r)); + + return 0; +} + +void register_tests(void) +{ + REGISTER(parser_proper_test_1); + REGISTER(parser_proper_test_2); +} diff --git a/src/test.cpp b/src/test.cpp new file mode 100644 index 0000000..478c1d1 --- /dev/null +++ b/src/test.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include "test-cases.cpp" + +typedef int (*TestFunc)(void); + +void register_tests(void); + +std::vector test_funcs; + +int main(void) +{ + static unsigned long fails = 0, + successes = 0; + + register_tests(); + + for (int i = 0, l = test_funcs.size(); i < l; i++) { + std::cout << "--- Running test " << i + 1 << "/" << l << "\n"; + if (test_funcs[i]()) + { + std::cout << "Test failed.\n"; + fails++; + } + else + { + successes++; + } + } + + std::cout << "--- Finished testing: " << successes << " passed, " << fails << " failed\n"; + + return !!fails; +}