initial commit

This commit is contained in:
2026-10-02 20:31:24 +03:00
commit 2999c295af
9 changed files with 641 additions and 0 deletions
+37
View File
@@ -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
)
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#define DEBUG 1
+157
View File
@@ -0,0 +1,157 @@
#pragma once
#include <iostream>
#include <memory>
#include <string>
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<std::string> s;
double d;
Operator o;
explicit TokenValue(std::string& s)
: s(std::make_shared<std::string>(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<Token> l; // left hand
std::shared_ptr<Token> o; // operation
std::shared_ptr<Token> r; // right hand
};
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <vector>
#include "expression.hpp"
namespace Parser {
std::shared_ptr<std::vector<Expression::Token>> tokenize_input(char *input_string);
int assert_same(std::vector<Expression::Token>& a, std::vector<Expression::Token>& b);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <iostream>
#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"
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <iostream>
#include <vector>
typedef int (*TestFunc)(void);
extern std::vector<TestFunc> 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)
+312
View File
@@ -0,0 +1,312 @@
#include <iostream>
#include <cctype>
#include <cstring>
#include <memory>
#include <vector>
#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<std::vector<Expression::Token>>& pv;
Parser(std::shared_ptr<std::vector<Expression::Token>>& 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<std::vector<Expression::Token>> tokenize_input(char *input_string)
{
std::shared_ptr<std::vector<Expression::Token>> pv = std::make_shared<std::vector<Expression::Token>>();
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<Expression::Token>& a, std::vector<Expression::Token>& 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;
}
}
+55
View File
@@ -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<Expression::Token> 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<Expression::Token> 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);
}
+35
View File
@@ -0,0 +1,35 @@
#include <iostream>
#include <vector>
#include "test-cases.cpp"
typedef int (*TestFunc)(void);
void register_tests(void);
std::vector<TestFunc> 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;
}