commit a3e44d6a58655172be16b73a466ccc3b2f945b1a Author: SkrinVex Date: Thu Dec 25 12:36:05 2025 +0500 Первый diff --git a/script.fox b/script.fox new file mode 100644 index 0000000..08fe969 --- /dev/null +++ b/script.fox @@ -0,0 +1,14 @@ +{ + print("=== Welcome to FoxLang ==="); + + print("Math test:"); + print(20 + 30 * 2); + + print("Type something:"); + print(input()); + + { + print("I am inside a block!"); + } + +} diff --git a/src/AST.h b/src/AST.h new file mode 100644 index 0000000..01bb260 --- /dev/null +++ b/src/AST.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include +#include +#include + +// Хелпер для красивого вывода чисел +static std::string formatNumber(double val) { + std::string s = std::to_string(val); + // Удаляем лишние нули и точку, если число целое + s.erase(s.find_last_not_of('0') + 1, std::string::npos); + if (s.back() == '.') s.pop_back(); + return s; +} + +struct Node { + virtual ~Node() = default; + virtual std::string eval() = 0; +}; + +struct NumberNode : Node { + std::string val; + NumberNode(std::string v) : val(v) {} + std::string eval() override { return val; } +}; + +struct BinOpNode : Node { + char op; + std::unique_ptr left, right; + BinOpNode(char o, std::unique_ptr l, std::unique_ptr r) + : op(o), left(std::move(l)), right(std::move(r)) {} + + std::string eval() override { + double l = std::stod(left->eval()); + double r = std::stod(right->eval()); + double res = 0; + if (op == '+') res = l + r; + else if (op == '-') res = l - r; + else if (op == '*') res = l * r; + else if (op == '/') res = (r != 0) ? l / r : 0; + return formatNumber(res); + } +}; + +struct RoundNode : Node { + std::unique_ptr expr; + RoundNode(std::unique_ptr e) : expr(std::move(e)) {} + std::string eval() override { + double val = std::stod(expr->eval()); + return formatNumber(std::round(val)); + } +}; + +struct RandomNode : Node { + std::string eval() override { + return std::to_string(rand() % 100); // Случайное от 0 до 99 + } +}; + +struct PrintNode : Node { + std::unique_ptr expr; + PrintNode(std::unique_ptr e) : expr(std::move(e)) {} + std::string eval() override { + std::cout << expr->eval() << std::endl; + return ""; + } +}; + +struct InputNode : Node { + std::string eval() override { + std::string buffer; + std::getline(std::cin, buffer); + return buffer; + } +}; diff --git a/src/Lexer.cpp b/src/Lexer.cpp new file mode 100644 index 0000000..049b842 --- /dev/null +++ b/src/Lexer.cpp @@ -0,0 +1,63 @@ +#include "Lexer.h" +#include +#include + +Lexer::Lexer(std::string src) : source(src) {} + +std::vector Lexer::tokenize() { + std::vector tokens; + while (pos < source.length()) { + char current = source[pos]; + + if (isspace(current)) { + if (current == '\n') line++; + pos++; + } + else if (isdigit(current)) { + std::string num; + while (pos < source.length() && isdigit(source[pos])) num += source[pos++]; + // Точка для дробных чисел (например, 3.14) + if (pos < source.length() && source[pos] == '.') { + num += source[pos++]; + while (pos < source.length() && isdigit(source[pos])) num += source[pos++]; + } + tokens.push_back({TokenType::NUMBER, num, line}); + } + else if (current == '"') { + pos++; std::string str; + while (pos < source.length() && source[pos] != '"') str += source[pos++]; + pos++; + tokens.push_back({TokenType::STRING, str, line}); + } + else if (isalpha(current)) { + std::string id; + while (pos < source.length() && isalpha(source[pos])) id += source[pos++]; + + if (id == "print") tokens.push_back({TokenType::PRINT, id, line}); + else if (id == "input") tokens.push_back({TokenType::INPUT, id, line}); + else if (id == "round") tokens.push_back({TokenType::ROUND, id, line}); + else if (id == "random") tokens.push_back({TokenType::RANDOM, id, line}); + else if (id == "fox") tokens.push_back({TokenType::FOX, id, line}); + else tokens.push_back({TokenType::IDENTIFIER, id, line}); // Неизвестное слово + } + else { + switch (current) { + case '+': tokens.push_back({TokenType::PLUS, "+", line}); break; + case '-': tokens.push_back({TokenType::MINUS, "-", line}); break; + case '*': tokens.push_back({TokenType::STAR, "*", line}); break; + case '/': tokens.push_back({TokenType::SLASH, "/", line}); break; + case '(': tokens.push_back({TokenType::LPAREN, "(", line}); break; + case ')': tokens.push_back({TokenType::RPAREN, ")", line}); break; + case '{': tokens.push_back({TokenType::LBRACE, "{", line}); break; + case '}': tokens.push_back({TokenType::RBRACE, "}", line}); break; + case ';': tokens.push_back({TokenType::SEMICOLON, ";", line}); break; + default: + std::cerr << "Lexer Error: Unknown char '" << current << "' at line " << line << std::endl; + exit(1); + } + pos++; + } + } + tokens.push_back({TokenType::END, "", line}); + return tokens; +} diff --git a/src/Lexer.h b/src/Lexer.h new file mode 100644 index 0000000..0881d92 --- /dev/null +++ b/src/Lexer.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include "Token.h" + +class Lexer { + std::string source; + size_t pos = 0; + int line = 1; + +public: + Lexer(std::string src); + std::vector tokenize(); +}; diff --git a/src/Parser.cpp b/src/Parser.cpp new file mode 100644 index 0000000..b507e5d --- /dev/null +++ b/src/Parser.cpp @@ -0,0 +1,111 @@ +#include "Parser.h" +#include + +Parser::Parser(std::vector t) : tokens(t) {} + +Token Parser::consume(TokenType type) { + if (tokens[pos].type == type) return tokens[pos++]; + std::cerr << "Syntax Error: Expected token type " << (int)type + << " but found '" << tokens[pos].value + << "' on line " << tokens[pos].line << std::endl; + exit(1); +} + +// Выражения (возвращают значение) +std::unique_ptr Parser::primary() { + if (tokens[pos].type == TokenType::NUMBER) return std::make_unique(consume(TokenType::NUMBER).value); + if (tokens[pos].type == TokenType::STRING) return std::make_unique(consume(TokenType::STRING).value); + + // input() + if (tokens[pos].type == TokenType::INPUT) { + consume(TokenType::INPUT); consume(TokenType::LPAREN); consume(TokenType::RPAREN); + return std::make_unique(); + } + + // round( expr ) + if (tokens[pos].type == TokenType::ROUND) { + consume(TokenType::ROUND); + consume(TokenType::LPAREN); + auto node = expression(); + consume(TokenType::RPAREN); + return std::make_unique(std::move(node)); + } + + // random() + if (tokens[pos].type == TokenType::RANDOM) { + consume(TokenType::RANDOM); consume(TokenType::LPAREN); consume(TokenType::RPAREN); + return std::make_unique(); + } + + if (tokens[pos].type == TokenType::LPAREN) { + consume(TokenType::LPAREN); + auto node = expression(); + consume(TokenType::RPAREN); + return node; + } + + std::cerr << "Error: Unexpected token '" << tokens[pos].value << "' in expression at line " << tokens[pos].line << std::endl; + exit(1); +} + +std::unique_ptr Parser::multiplication() { + auto node = primary(); + while (tokens[pos].type == TokenType::STAR || tokens[pos].type == TokenType::SLASH) { + char op = tokens[pos].value[0]; pos++; + node = std::make_unique(op, std::move(node), primary()); + } + return node; +} + +std::unique_ptr Parser::expression() { + auto node = multiplication(); + while (tokens[pos].type == TokenType::PLUS || tokens[pos].type == TokenType::MINUS) { + char op = tokens[pos].value[0]; pos++; + node = std::make_unique(op, std::move(node), multiplication()); + } + return node; +} + +// Блоки кода +void Parser::block() { + consume(TokenType::LBRACE); + while (tokens[pos].type != TokenType::RBRACE && tokens[pos].type != TokenType::END) { + statement(); + } + consume(TokenType::RBRACE); +} + +// Инструкции (заканчиваются ;) +void Parser::statement() { + if (tokens[pos].type == TokenType::PRINT) { + consume(TokenType::PRINT); + consume(TokenType::LPAREN); + auto expr = expression(); + consume(TokenType::RPAREN); + consume(TokenType::SEMICOLON); + auto pNode = std::make_unique(std::move(expr)); + pNode->eval(); + } + else if (tokens[pos].type == TokenType::FOX) { + consume(TokenType::FOX); + consume(TokenType::LPAREN); // fox ( + consume(TokenType::RPAREN); // ) + consume(TokenType::SEMICOLON); // ; + std::cout << " (\\_/) \n (o.o) \n (> <) OFOX!" << std::endl; + } + else if (tokens[pos].type == TokenType::LBRACE) { + block(); + } + else { + // ОБРАБОТКА ОШИБОК: Если встретили слово, которое не является командой + std::cerr << "Runtime Error: Unknown command or function '" << tokens[pos].value + << "' at line " << tokens[pos].line << std::endl; + exit(1); + } +} + +void Parser::run() { + while (tokens[pos].type != TokenType::END) { + statement(); + } +} diff --git a/src/Parser.h b/src/Parser.h new file mode 100644 index 0000000..0655779 --- /dev/null +++ b/src/Parser.h @@ -0,0 +1,21 @@ +#pragma once +#include "Token.h" +#include "AST.h" +#include +#include + +class Parser { + std::vector tokens; + size_t pos = 0; + + Token consume(TokenType type); + std::unique_ptr primary(); + std::unique_ptr multiplication(); + std::unique_ptr expression(); + void statement(); // Обработка отдельной команды с точкой с запятой + void block(); // Обработка блока { ... } + +public: + Parser(std::vector t); + void run(); +}; diff --git a/src/Token.h b/src/Token.h new file mode 100644 index 0000000..9934423 --- /dev/null +++ b/src/Token.h @@ -0,0 +1,19 @@ +#pragma once +#include + +enum class TokenType { + NUMBER, STRING, + PLUS, MINUS, STAR, SLASH, + LPAREN, RPAREN, + LBRACE, RBRACE, SEMICOLON, + PRINT, INPUT, + ROUND, FOX, RANDOM, // Новые команды + IDENTIFIER, // Для неизвестных слов + END, ERROR +}; + +struct Token { + TokenType type; + std::string value; + int line; +}; diff --git a/src/foxlang b/src/foxlang new file mode 100755 index 0000000..1e8d6e7 Binary files /dev/null and b/src/foxlang differ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..27ea438 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include "Lexer.h" +#include "Parser.h" + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::cout << "Usage: foxlang " << std::endl; + return 1; + } + + std::ifstream file(argv[1]); + if (!file.is_open()) { + std::cerr << "Error: File not found!" << std::endl; + return 1; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + + Lexer lexer(buffer.str()); + auto tokens = lexer.tokenize(); + + Parser parser(tokens); + parser.run(); + + return 0; +} diff --git a/test.fox b/test.fox new file mode 100644 index 0000000..0e9e967 --- /dev/null +++ b/test.fox @@ -0,0 +1,6 @@ +{ + + print("Добро пожаловать в FoxLang"); + print(input() + input()); + +}