#include #include #include #include #include #include #include #include #include #include "../libs/linenoise.hpp" enum class ValueTypes { Identifier, Int, Double, String, List, Nil }; enum class ParserErrorType { UnexpectedEndOfInput, UnexpectedToken, MissingOpenParen, MissingCloseParen, UnexpectedTopLevelToken }; enum class InterpreterErrorType { UnknownInstruction, UnexpectedToken, IncorrectTokenType, MathError, UnknownEnvironment }; class InterpretingError : public std::exception { std::string message; InterpreterErrorType errorType; public: explicit InterpretingError(InterpreterErrorType errorType) : errorType(errorType) { std::stringstream oss; oss << "Interpreting error: "; switch (errorType) { case InterpreterErrorType::UnexpectedToken: oss << "Unexpected token"; break; case InterpreterErrorType::UnknownInstruction: oss << "Unknown instruction"; break; case InterpreterErrorType::IncorrectTokenType: oss << "Incorrect token type"; break; case InterpreterErrorType::MathError: oss << "Math error"; break; case InterpreterErrorType::UnknownEnvironment: oss << "Unknown environment"; break; } message = oss.str(); } [[nodiscard]] const char* what() const noexcept override { return message.c_str(); } }; class ParsingError : public std::exception { std::string message; ParserErrorType errorType; int position; std::string sourceCode; public: ParsingError(ParserErrorType type, int pos, std::string source = "") : errorType(type), position(pos), sourceCode(std::move(source)) { std::ostringstream oss; oss << "Parsing error at position " << pos << ": "; switch (type) { case ParserErrorType::UnexpectedEndOfInput: oss << "Unexpected end of input"; break; case ParserErrorType::UnexpectedToken: oss << "Unexpected token"; break; case ParserErrorType::MissingOpenParen: oss << "Expected '(' at start of expression"; break; case ParserErrorType::MissingCloseParen: oss << "Missing closing ')'"; break; case ParserErrorType::UnexpectedTopLevelToken: oss << "Unexpected token at top level (expected '(')"; break; } message = oss.str(); } [[nodiscard]] const char* what() const noexcept override { return message.c_str(); } [[nodiscard]] ParserErrorType getErrorType() const { return errorType; } [[nodiscard]] int getPosition() const { return position; } [[nodiscard]] const std::string& getSourceCode() const { return sourceCode; } }; class Value { std::variant> value; public: ValueTypes type; std::optional getInt() { if (std::holds_alternative(value)) { return std::get(value); } return {}; } std::optional getDouble() { if (std::holds_alternative(value)) { return std::get(value); } return {}; } std::optional getString() { if (std::holds_alternative(value)) { return std::get(value); } return {}; } std::optional> getList() { if (std::holds_alternative>(value)) { return std::get>(value); } return {}; } void print() { switch (type) { case ValueTypes::String: { std::cout << getString().value(); break; } case ValueTypes::Int: { std::cout << getInt().value(); break; } case ValueTypes::Double: { std::cout << getDouble().value(); break; } case ValueTypes::List: { auto list = getList().value(); bool first = true; for (auto& listElement : list) { if (!first) { std::cout << ", "; } else { first = false; } listElement.print(); } } case ValueTypes::Nil: { std::cout << "\033[2;3;96m" << "nil" << "\033[0m"; } } } bool operator==(Value &otherValue) { if (type != otherValue.type) { return false; } switch (type) { case ValueTypes::String: { if (getString() == otherValue.getString()) { return true; } return false; } case ValueTypes::Int: { if (getInt() == otherValue.getInt()) { return true; } return false; } case ValueTypes::Double: { if (getDouble() == otherValue.getDouble()) { return true; } return false; } case ValueTypes::List: { auto list = getList().value(); auto compareList = otherValue.getList().value(); if (list.size() != compareList.size()) { return false; } for (int i = 0; i < list.size(); i++) { if (list[i] == compareList[i]) { return true; } return false; } return true; } default: return false; } } explicit Value() : value(nullptr), type(ValueTypes::Nil) {} explicit Value(int in) : value(in), type(ValueTypes::Int) {} explicit Value(double in) : value(in), type(ValueTypes::Double) {} explicit Value(std::string in) : value(in), type(ValueTypes::String) {} explicit Value(std::vector in) : value(in), type(ValueTypes::List) {} Value(std::string in, bool isIdentifier) : value(in), type(isIdentifier ? ValueTypes::Identifier : ValueTypes::String) {} }; class Instruction { public: std::string instruction; std::vector> args; Instruction(std::string instruction, std::vector> args) : instruction(std::move(instruction)), args(std::move(args)) {} Instruction() = default; }; class Parser { std::string input; std::vector split; int currentChar = -1; int current = -1; std::optional consumeChar() { currentChar++; if (currentChar >= input.size()) { return {}; } return input[currentChar]; } std::optional consume() { current++; if (current >= split.size()) { return {}; } return split[current]; } [[nodiscard]] std::optional peek() const { if (current + 1 >= split.size()) { return {}; } return split[current + 1]; } // Helper to detect value type static Value parseValue(const std::string& token) { // If it's nil, just return nil if (token == "nil") { return Value(); } // Check if it's a string literal if (token.front() == '"' && token.back() == '"') { return Value(token.substr(1, token.length() - 2)); } // Try to parse as integer bool isInt = true; bool isDouble = false; for (size_t i = 0; i < token.length(); i++) { char c = token[i]; if (i == 0 && c == '-') continue; // Allow negative sign if (c == '.') { if (isDouble) { // Second dot isInt = false; break; } isDouble = true; isInt = false; } else if (!std::isdigit(c)) { isInt = false; isDouble = false; break; } } if (isInt) { return Value(std::stoi(token)); } if (isDouble) { return Value(std::stod(token)); } // If it isn't any value, return an identifier return {token, true}; } // Recursive parser for expressions Instruction parseExpression() { auto topt = consume(); if (!topt) { throw ParsingError(ParserErrorType::UnexpectedEndOfInput, current, input); } std::string token = topt.value(); if (token != "(") { throw ParsingError(ParserErrorType::MissingOpenParen, current, input); } topt = consume(); if (!topt) { throw ParsingError(ParserErrorType::UnexpectedEndOfInput, current, input); } std::string instruction = topt.value(); std::vector> args; while (true) { topt = peek(); if (!topt) { throw ParsingError(ParserErrorType::MissingCloseParen, current, input); } token = topt.value(); if (token == ")") { consume(); break; } else if (token == "(") { args.emplace_back(parseExpression()); } else { consume(); args.emplace_back(parseValue(token)); } } return {instruction, args}; } public: std::vector instructions; explicit Parser(std::string in) : input(std::move(in)) { // Lexer logic std::string buf; bool inString = false; while (auto copt = consumeChar()) { char c = copt.value(); if (c != '"' && inString) { buf.push_back(c); continue; } switch (c) { case '(': case ')': { if (!buf.empty()) { split.push_back(buf); buf.clear(); } split.emplace_back(1, c); break; } case ' ': case '\n': if (!buf.empty()) { split.push_back(buf); buf.clear(); } break; case '"': { inString = !inString; buf.push_back('"'); if (!inString) { split.push_back(buf); buf.clear(); } break; } default: { buf.push_back(c); } } } // Parser logic - parse all top-level expressions while (peek()) { auto token = peek().value(); if (token == "(") { instructions.push_back(parseExpression()); } else { throw ParsingError(ParserErrorType::UnexpectedTopLevelToken, current + 1, input); } } } Parser() = default; }; class Interpreter { public: std::vector instructions = {}; std::map environment = {{"pippleVersion", Value("0.0.1")}}; Value interpretInstruction(Instruction& instruction) { // Preprocess identifiers for (auto& arg : instruction.args) { if (std::holds_alternative(arg) && instruction.instruction != "let" && instruction.instruction != "set") { if (std::get(arg).type == ValueTypes::Identifier) { std::string id = std::get(arg).getString().value(); if (!environment.contains(id)) { throw InterpretingError(InterpreterErrorType::UnknownEnvironment); } arg = environment[id]; } } } // Instructions at the top of this function require input to not be preprocessed if (instruction.instruction == "if") { if (instruction.args.size() < 2) { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } bool toContinue = true; if (std::holds_alternative(instruction.args[0])) { toContinue = interpretInstruction(std::get(instruction.args[0])).type != ValueTypes::Nil; } if (std::holds_alternative(instruction.args[0])) { toContinue = std::get(instruction.args[0]).type != ValueTypes::Nil; } if (toContinue) { bool first = true; Value returnValue; for (auto& arg : instruction.args) { if (first) { first = false; } else { if (std::holds_alternative(arg)) { returnValue = interpretInstruction(std::get(arg)); } } } return returnValue; } return Value(); } if (instruction.instruction == "while") { if (instruction.args.size() < 2) { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } while (true) { Value conditionResult; if (std::holds_alternative(instruction.args[0])) { Instruction condCopy = std::get(instruction.args[0]); conditionResult = interpretInstruction(condCopy); } else if (std::holds_alternative(instruction.args[0])) { conditionResult = std::get(instruction.args[0]); } if (conditionResult.type == ValueTypes::Nil) { break; } for (size_t i = 1; i < instruction.args.size(); i++) { if (std::holds_alternative(instruction.args[i])) { Instruction bodyCopy = std::get(instruction.args[i]); if (bodyCopy.instruction == "break") { return Value(); } interpretInstruction(bodyCopy); } } } return Value(); } // Preprocess instructions inside instructions for (auto &arg : instruction.args) { if (std::holds_alternative(arg)) { arg = interpretInstruction(std::get(arg)); } } // Instructions that don't require preprocessing go below this line // It is safe to use std::get(...) on all arguments if (instruction.instruction == "+" || instruction.instruction == "add") { double result = 0; for (const auto& arg : instruction.args) { auto value = std::get(arg); switch (value.type) { case ValueTypes::Int: { result += value.getInt().value(); break; } case ValueTypes::Double: { result += value.getDouble().value(); break; } default: { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } } } if (result == static_cast(result)) { return Value(static_cast(result)); } return Value(result); } if (instruction.instruction == "-" || instruction.instruction == "subtract") { if (instruction.args.empty()) return Value(0); auto firstValue = std::get(instruction.args[0]); double result = 0; if (firstValue.type == ValueTypes::Int) { result = firstValue.getInt().value(); } else if (firstValue.type == ValueTypes::Double) { result = firstValue.getDouble().value(); } else { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } for (size_t i = 1; i < instruction.args.size(); i++) { auto value = std::get(instruction.args[i]); if (value.type == ValueTypes::Int) { result -= value.getInt().value(); } else if (value.type == ValueTypes::Double) { result -= value.getDouble().value(); } else { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } } if (result == static_cast(result)) { return Value(static_cast(result)); } return Value(result); } if (instruction.instruction == "*" || instruction.instruction == "multiply") { if (instruction.args.empty()) return Value(1); auto firstValue = std::get(instruction.args[0]); double result = 0; if (firstValue.type == ValueTypes::Int) { result = firstValue.getInt().value(); } else if (firstValue.type == ValueTypes::Double) { result = firstValue.getDouble().value(); } else { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } for (size_t i = 1; i < instruction.args.size(); i++) { auto value = std::get(instruction.args[i]); if (value.type == ValueTypes::Int) { result *= value.getInt().value(); } else if (value.type == ValueTypes::Double) { result *= value.getDouble().value(); } else { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } } if (result == static_cast(result)) { return Value(static_cast(result)); } return Value(result); } if (instruction.instruction == "/" || instruction.instruction == "divide") { if (instruction.args.empty()) return Value(1); auto firstValue = std::get(instruction.args[0]); double result = 0; if (firstValue.type == ValueTypes::Int) { result = firstValue.getInt().value(); } else if (firstValue.type == ValueTypes::Double) { result = firstValue.getDouble().value(); } else { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } for (size_t i = 1; i < instruction.args.size(); i++) { auto value = std::get(instruction.args[i]); if (value.type == ValueTypes::Int) { if (value.getInt().value() == 0) { throw InterpretingError(InterpreterErrorType::MathError); } result /= value.getInt().value(); } else if (value.type == ValueTypes::Double) { if (value.getDouble().value() == 0) { throw InterpretingError(InterpreterErrorType::MathError); } result /= value.getDouble().value(); } else { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } } if (result == static_cast(result)) { return Value(static_cast(result)); } return Value(result); } if (instruction.instruction == "==" || instruction.instruction == "equal") { bool first = true; Value firstVal; for (const auto& arg : instruction.args) { auto value = std::get(arg); if (first) { first = false; firstVal = value; } else { if (value != firstVal) { return Value(); } } } return Value(1); } if (instruction.instruction == "!=" || instruction.instruction == "inequal") { bool first = true; Value firstVal; for (const auto& arg : instruction.args) { auto value = std::get(arg); if (first) { first = false; firstVal = value; } else { if (value == firstVal) { return Value(); } } } return Value(1); } if (instruction.instruction == "input") { std::string input; linenoise::Readline("", input); return Value(input); } if (instruction.instruction == "let") { if (instruction.args.size() < 2) { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } auto idval = std::get(instruction.args[0]); std::string id; if (idval.type == ValueTypes::Identifier) { id = idval.getString().value(); } else { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } environment[id] = std::get(instruction.args[1]); return {environment[id]}; } if (instruction.instruction == "set") { if (instruction.args.size() < 2) { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } auto idval = std::get(instruction.args[0]); std::string id; if (idval.type == ValueTypes::Identifier) { id = idval.getString().value(); } else { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); } if (!environment.contains(id)) { throw InterpretingError(InterpreterErrorType::UnknownEnvironment); } environment[id] = std::get(instruction.args[1]); return {environment[id]}; } if (instruction.instruction == "print") { for (const auto& arg : instruction.args) { auto value = std::get(arg); value.print(); std::cout << " "; } std::cout << "\n"; return Value(); } if (instruction.instruction == "log") { for (const auto& arg : instruction.args) { std::cout << "\033[2;3;93m"; auto value = std::get(arg); value.print(); std::cout << " "; } std::cout << "\033[0m\n"; return Value(); } if (instruction.instruction == "exit") { if (!instruction.args.empty()) { auto value = std::get(instruction.args[0]); if (value.type == ValueTypes::Int) { exit(value.getInt().value()); } } exit(0); } throw InterpretingError(InterpreterErrorType::UnknownInstruction); } explicit Interpreter(std::vector in) : instructions(std::move(in)) { for (Instruction &instruction : instructions) { interpretInstruction(instruction); } } Interpreter() = default; }; int main(int argc, char** argv) { bool isInteractive = true; std::string program; if (argc > 1) { isInteractive = false; std::ifstream ifs(argv[1]); std::stringstream buffer; if (ifs.is_open()) { buffer << ifs.rdbuf(); } program = buffer.str(); } linenoise::SetMultiLine(true); linenoise::SetHistoryMaxLen(50); Parser parser; Interpreter interpreter; while (true) { if (isInteractive) { if (linenoise::Readline("pipple> ", program)) { return 0; } } try { parser = Parser(program); } catch (const ParsingError& e) { std::cerr << "Parse error: " << e.what() << std::endl; // Show code context const std::string& source = e.getSourceCode(); if (!source.empty()) { int pos = e.getPosition(); // Calculate character position in source from token position int charPos = 0; int tokenCount = 0; bool inString = false; for (size_t i = 0; i < source.length() && tokenCount < pos; i++) { char c = source[i]; if (c == '"') inString = !inString; if (!inString && (c == '(' || c == ')' || c == ' ' || c == '\n')) { if (i > 0 && source[i-1] != ' ' && source[i-1] != '\n' && source[i-1] != '(' && source[i-1] != ')') { tokenCount++; } if (c == '(' || c == ')') tokenCount++; } charPos = static_cast(i); } // Show context (30 chars before and after) int start = std::max(0, charPos - 30); int end = std::min(static_cast(source.length()), charPos + 30); std::cerr << "\nCode context:\n"; if (start > 0) std::cerr << "..."; std::cerr << source.substr(start, end - start); if (end < static_cast(source.length())) std::cerr << "..."; std::cerr << "\n"; // Show pointer to error position int pointerPos = (start > 0 ? 3 : 0) + (charPos - start); std::cerr << std::string(pointerPos, ' ') << "^\n"; } if (!isInteractive) return 1; continue; } catch (const std::exception& e) { std::cerr << "Unexpected error: " << e.what() << std::endl; if (!isInteractive) return 1; } try { for (auto instruction : parser.instructions) { interpreter.interpretInstruction(instruction); } } catch (const InterpretingError& e) { std::cerr << e.what() << std::endl; if (!isInteractive) return 1; } if (!isInteractive) { break; } } return 0; }