diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 61143a9..2904ddf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,46 +1,75 @@ -name: Build FoxLang +name: Build and Release FoxLang on: push: - branches: ["main", "master"] - pull_request: - branches: ["main", "master"] + branches: ["main"] + tags: + - "v*" # Срабатывает, когда ты пушишь тег, например v5.0.2 + +permissions: + contents: write # Разрешаем создавать релизы jobs: - # --- Сборка для Linux (Ubuntu) --- + # --- 1. Сборка Linux --- build-linux: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Compile with G++ run: | - g++ -std=c++17 src/main.cpp src/Lexer.cpp src/Parser.cpp -o foxlang + # Создаем папку build, чтобы туда сложить бинарник + mkdir build + g++ -std=c++17 src/main.cpp src/Lexer.cpp src/Parser.cpp -o build/foxlang - name: Upload Linux Artifact uses: actions/upload-artifact@v4 with: name: foxlang-linux - path: foxlang + path: build/foxlang - # --- Сборка для Windows (MSVC) --- + # --- 2. Сборка Windows --- build-windows: runs-on: windows-latest steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 - - name: Compile with CL (MSVC) - # Используем флаги: /EHsc (обработка исключений), /std:c++17, /Fe (имя выходного файла) + - name: Compile with CL + # Добавил создание папки build для порядка run: | - cl /EHsc /std:c++17 src/main.cpp src/Lexer.cpp src/Parser.cpp /Fefoxlang.exe + mkdir build + cl /EHsc /std:c++17 src/main.cpp src/Lexer.cpp src/Parser.cpp /Febuild/foxlang.exe - name: Upload Windows Artifact uses: actions/upload-artifact@v4 with: name: foxlang-windows - path: foxlang.exe + path: build/foxlang.exe + + # --- 3. Публикация Релиза (Только если есть тег) --- + release: + needs: [build-linux, build-windows] # Ждем окончания сборок + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Download Linux Artifact + uses: actions/download-artifact@v4 + with: + name: foxlang-linux + + - name: Download Windows Artifact + uses: actions/download-artifact@v4 + with: + name: foxlang-windows + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: | + foxlang + foxlang.exe + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml deleted file mode 100644 index fbf32ec..0000000 --- a/.github/workflows/c-cpp.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: C/C++ CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: configure - run: ./configure - - name: make - run: make - - name: make check - run: make check - - name: make distcheck - run: make distcheck diff --git a/src/AST.h b/src/AST.h index 377935b..617c80c 100644 --- a/src/AST.h +++ b/src/AST.h @@ -19,6 +19,12 @@ #include #endif +#ifdef _WIN32 + #include + #define popen _popen + #define pclose _pclose +#endif + struct Node; struct FuncParam { @@ -48,7 +54,7 @@ struct Context { if (parent) return parent->getVar(name); throw std::runtime_error("Runtime Error: Variable '" + name + "' not found!"); } - + std::vector& getArray(const std::string& name) { if (arrays.count(name)) return arrays[name]; if (parent) return parent->getArray(name); @@ -70,9 +76,9 @@ struct Context { } void setVar(const std::string& name, Value val) { - if (variables.count(name)) { + if (variables.count(name)) { variables[name].value = val.value; - return; + return; } if (parent) { parent->setVar(name, val); return; } throw std::runtime_error("Error: Variable '" + name + "' not defined!"); @@ -104,7 +110,7 @@ struct FuncDefNode : Node { std::vector params; std::shared_ptr body; - FuncDefNode(std::string rt, std::string n, std::vector p, std::shared_ptr b) + FuncDefNode(std::string rt, std::string n, std::vector p, std::shared_ptr b) : returnType(rt), name(n), params(p), body(b) {} Value eval(Context& ctx) override { return {"void", ""}; } @@ -115,7 +121,7 @@ struct ReturnNode : Node { ReturnNode(std::unique_ptr e) : expr(std::move(e)) {} Value eval(Context& ctx) override { Value result = expr ? expr->eval(ctx) : Value{"void", ""}; - throw ReturnValue{result}; + throw ReturnValue{result}; } }; @@ -123,7 +129,7 @@ struct FuncCallNode : Node { std::string name; std::vector> args; - FuncCallNode(std::string n, std::vector> a) + FuncCallNode(std::string n, std::vector> a) : name(n), args(std::move(a)) {} Value eval(Context& ctx) override { @@ -145,7 +151,7 @@ struct FuncCallNode : Node { // Вывести приглашение std::cout << args[0]->eval(ctx).value; } - std::string input; + std::string input; std::getline(std::cin, input); return {"string", input}; } @@ -200,24 +206,24 @@ struct FuncCallNode : Node { if (filenameVal.type != "string") { throw std::runtime_error("read_file() requires string filename"); } - + std::ifstream file(filenameVal.value); if (!file.is_open()) { return {"string", ""}; // Возвращаем пустую строку при ошибке } - + std::string line; while (std::getline(file, line)) { // Пропускаем комментарии и пустые строки if (line.empty() || line[0] == '#') continue; - + // Если строка не пустая и не комментарий, возвращаем её if (!line.empty()) { file.close(); return {"string", line}; } } - + file.close(); return {"string", ""}; } @@ -226,30 +232,30 @@ struct FuncCallNode : Node { if (urlVal.type != "string") { throw std::runtime_error("http_get() requires string URL"); } - + // Простая реализация через system curl std::string cmd = "curl -s \"" + urlVal.value + "\""; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) { return {"string", ""}; } - + std::string result; char buffer[128]; while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { result += buffer; } pclose(pipe); - + return {"string", result}; } if (name == "json_get" && args.size() == 2) { Value jsonVal = args[0]->eval(ctx); Value keyVal = args[1]->eval(ctx); - + std::string json = jsonVal.value; std::string key = keyVal.value; - + // Простой JSON парсер для Telegram API if (key == "chat_id") { size_t pos = json.find("\"chat\":{\"id\":"); @@ -279,7 +285,7 @@ struct FuncCallNode : Node { lastPos = pos; pos = json.find("\"update_id\":", pos + 1); } - + if (lastPos != 0) { lastPos += 12; // длина "\"update_id\":" size_t end = json.find(",", lastPos); @@ -288,16 +294,16 @@ struct FuncCallNode : Node { } } } - + return {"string", ""}; } if (name == "str_contains" && args.size() == 2) { Value textVal = args[0]->eval(ctx); Value substrVal = args[1]->eval(ctx); - + std::string text = textVal.value; std::string substr = substrVal.value; - + bool found = text.find(substr) != std::string::npos; return {"bool", found ? "true" : "false"}; } @@ -306,7 +312,7 @@ struct FuncCallNode : Node { if (strVal.type != "string") { return {"int", "0"}; } - + try { int result = std::stoi(strVal.value); return {"int", std::to_string(result)}; @@ -319,7 +325,7 @@ struct FuncCallNode : Node { std::string cmd = "curl -s \"" + urlVal.value + "\""; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) return {"string", ""}; - + std::string result; char buffer[128]; while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { @@ -332,11 +338,11 @@ struct FuncCallNode : Node { Value urlVal = args[0]->eval(ctx); Value dataVal = args[1]->eval(ctx); std::string contentType = args.size() > 2 ? args[2]->eval(ctx).value : "application/json"; - + std::string cmd = "curl -s -X POST -H \"Content-Type: " + contentType + "\" -d \"" + dataVal.value + "\" \"" + urlVal.value + "\""; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) return {"string", ""}; - + std::string result; char buffer[128]; while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { @@ -349,11 +355,11 @@ struct FuncCallNode : Node { Value urlVal = args[0]->eval(ctx); Value dataVal = args[1]->eval(ctx); std::string contentType = args.size() > 2 ? args[2]->eval(ctx).value : "application/json"; - + std::string cmd = "curl -s -X PUT -H \"Content-Type: " + contentType + "\" -d \"" + dataVal.value + "\" \"" + urlVal.value + "\""; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) return {"string", ""}; - + std::string result; char buffer[128]; while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { @@ -367,7 +373,7 @@ struct FuncCallNode : Node { std::string cmd = "curl -s -X DELETE \"" + urlVal.value + "\""; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) return {"string", ""}; - + std::string result; char buffer[128]; while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { @@ -380,38 +386,38 @@ struct FuncCallNode : Node { // FastAPI-подобные функции if (name == "server_start" && args.size() == 1) { int port = std::stoi(args[0]->eval(ctx).value); - + // Простая заглушка сервера std::cout << "HTTP Server started on port " << port << std::endl; std::cout << "Note: This is a simulation. Real server implementation requires additional setup." << std::endl; - + return {"string", "Server started on port " + std::to_string(port)}; } - + if (name == "server_stop" && args.size() == 0) { std::cout << "HTTP Server stopped" << std::endl; return {"string", "Server stopped"}; } - + if (name == "route_get" && args.size() == 2) { Value pathVal = args[0]->eval(ctx); Value handlerVal = args[1]->eval(ctx); - + std::cout << "Registered GET route: " << pathVal.value << " -> " << handlerVal.value << std::endl; return {"string", "GET route registered: " + pathVal.value}; } - + if (name == "route_post" && args.size() == 2) { Value pathVal = args[0]->eval(ctx); Value handlerVal = args[1]->eval(ctx); - + std::cout << "Registered POST route: " << pathVal.value << " -> " << handlerVal.value << std::endl; return {"string", "POST route registered: " + pathVal.value}; } - + if (name == "send_response" && args.size() == 1) { Value responseVal = args[0]->eval(ctx); - + std::cout << "HTTP Response: " << responseVal.value << std::endl; return {"void", ""}; } @@ -421,9 +427,9 @@ struct FuncCallNode : Node { if (!funcNodeBase) { throw std::runtime_error("Runtime Error: Function '" + name + "' not found!"); } - + FuncDefNode* funcDef = static_cast(funcNodeBase.get()); - + if (args.size() != funcDef->params.size()) { throw std::runtime_error("Args count mismatch for '" + name + "'"); } @@ -441,7 +447,7 @@ struct FuncCallNode : Node { try { funcDef->body->eval(funcScope); } catch (const ReturnValue& ret) { - return ret.value; + return ret.value; } return {"void", ""}; @@ -478,7 +484,7 @@ struct VarAccessNode : Node { struct VarDeclNode : Node { std::string type, name; std::unique_ptr expr; - VarDeclNode(std::string t, std::string n, std::unique_ptr e) + VarDeclNode(std::string t, std::string n, std::unique_ptr e) : type(t), name(n), expr(std::move(e)) {} Value eval(Context& ctx) override { ctx.defineVar(name, type, expr->eval(ctx)); @@ -489,7 +495,7 @@ struct VarDeclNode : Node { struct GlobalVarDeclNode : Node { std::string type, name; std::unique_ptr expr; - GlobalVarDeclNode(std::string t, std::string n, std::unique_ptr e) + GlobalVarDeclNode(std::string t, std::string n, std::unique_ptr e) : type(t), name(n), expr(std::move(e)) {} Value eval(Context& ctx) override { Context* root = &ctx; @@ -512,13 +518,13 @@ struct VarAssignNode : Node { struct BinOpNode : Node { char op; std::unique_ptr left, right; - BinOpNode(char o, std::unique_ptr l, std::unique_ptr r) + BinOpNode(char o, std::unique_ptr l, std::unique_ptr r) : op(o), left(std::move(l)), right(std::move(r)) {} - + Value eval(Context& ctx) override { Value lval = left->eval(ctx); Value rval = right->eval(ctx); - + if (op == '+') { if (lval.type == "string" || rval.type == "string") { return {"string", lval.value + rval.value}; @@ -530,24 +536,24 @@ struct BinOpNode : Node { int l = std::stoi(lval.value), r = std::stoi(rval.value); return {"int", std::to_string(l + r)}; } - + if (op == '-' || op == '*' || op == '/' || op == '%') { if (lval.type == "float" || rval.type == "float") { double l = std::stod(lval.value), r = std::stod(rval.value); - double result = (op == '-') ? l - r : (op == '*') ? l * r : + double result = (op == '-') ? l - r : (op == '*') ? l * r : (op == '/') ? l / r : std::fmod(l, r); return {"float", formatNumber(result)}; } int l = std::stoi(lval.value), r = std::stoi(rval.value); - int result = (op == '-') ? l - r : (op == '*') ? l * r : + int result = (op == '-') ? l - r : (op == '*') ? l * r : (op == '/') ? l / r : l % r; return {"int", std::to_string(result)}; } - + if (op == '=' || op == '!' || op == '<' || op == '>') { bool result; if (lval.type == "string" && rval.type == "string") { - result = (op == '=') ? lval.value == rval.value : + result = (op == '=') ? lval.value == rval.value : (op == '!') ? lval.value != rval.value : (op == '<') ? lval.value < rval.value : lval.value > rval.value; } else { @@ -557,13 +563,13 @@ struct BinOpNode : Node { } return {"bool", result ? "true" : "false"}; } - + if (op == '&' || op == '|') { bool l = (lval.value == "true"), r = (rval.value == "true"); bool result = (op == '&') ? l && r : l || r; return {"bool", result ? "true" : "false"}; } - + return {"void", ""}; } }; @@ -606,7 +612,7 @@ struct ArrayDeclNode : Node { struct ArraySetNode : Node { std::string name; std::unique_ptr index, value; - ArraySetNode(std::string n, std::unique_ptr i, std::unique_ptr v) + ArraySetNode(std::string n, std::unique_ptr i, std::unique_ptr v) : name(n), index(std::move(i)), value(std::move(v)) {} Value eval(Context& ctx) override { int idx = std::stoi(index->eval(ctx).value); @@ -635,7 +641,7 @@ struct BlockNode : Node { struct IfNode : Node { std::unique_ptr condition, thenB, elseB; - IfNode(std::unique_ptr c, std::unique_ptr t, std::unique_ptr e = nullptr) + IfNode(std::unique_ptr c, std::unique_ptr t, std::unique_ptr e = nullptr) : condition(std::move(c)), thenB(std::move(t)), elseB(std::move(e)) {} Value eval(Context& ctx) override { bool cond = (condition->eval(ctx).value == "true"); @@ -647,7 +653,7 @@ struct IfNode : Node { struct WhileNode : Node { std::unique_ptr condition, body; - WhileNode(std::unique_ptr c, std::unique_ptr b) + WhileNode(std::unique_ptr c, std::unique_ptr b) : condition(std::move(c)), body(std::move(b)) {} Value eval(Context& ctx) override { while (condition->eval(ctx).value == "true") { @@ -665,7 +671,7 @@ struct WhileNode : Node { struct ForNode : Node { std::unique_ptr init, condition, step, body; - ForNode(std::unique_ptr i, std::unique_ptr c, std::unique_ptr s, std::unique_ptr b) + ForNode(std::unique_ptr i, std::unique_ptr c, std::unique_ptr s, std::unique_ptr b) : init(std::move(i)), condition(std::move(c)), step(std::move(s)), body(std::move(b)) {} Value eval(Context& ctx) override { if (init) init->eval(ctx); @@ -709,14 +715,14 @@ struct SwitchNode : Node { std::unique_ptr expr; std::vector, std::unique_ptr>> cases; // value, body std::unique_ptr defaultCase; - + SwitchNode(std::unique_ptr e) : expr(std::move(e)) {} - + Value eval(Context& ctx) override { Value switchValue = expr->eval(ctx); bool executed = false; bool fallthrough = false; - + for (auto& caseItem : cases) { if (!executed && !fallthrough) { Value caseValue = caseItem.first->eval(ctx); @@ -725,7 +731,7 @@ struct SwitchNode : Node { fallthrough = true; } } - + if (fallthrough) { try { caseItem.second->eval(ctx); @@ -735,11 +741,11 @@ struct SwitchNode : Node { } } } - + if (!executed && defaultCase) { defaultCase->eval(ctx); } - + return {"void", ""}; } }; @@ -754,32 +760,32 @@ struct InputNode : Node { struct ReadFileNode : Node { std::unique_ptr filename; ReadFileNode(std::unique_ptr fn) : filename(std::move(fn)) {} - + Value eval(Context& ctx) override { Value filenameVal = filename->eval(ctx); if (filenameVal.type != "string") { throw std::runtime_error("read_file() requires string filename"); } - + std::ifstream file(filenameVal.value); if (!file.is_open()) { return {"string", ""}; // Возвращаем пустую строку при ошибке } - + std::string content; std::string line; bool first = true; while (std::getline(file, line)) { // Пропускаем комментарии и пустые строки if (line.empty() || line[0] == '#') continue; - + // Если строка не пустая и не комментарий, возвращаем её if (!line.empty()) { file.close(); return {"string", line}; } } - + file.close(); return {"string", ""}; }