Files
pipple/src/main.cpp

1456 lines
54 KiB
C++
Raw Normal View History

2025-11-10 20:44:10 +11:00
#include <iostream>
#include <utility>
#include <vector>
#include <optional>
#include <variant>
#include <cctype>
#include <complex>
#include <sstream>
#include <map>
2025-11-15 13:52:23 +11:00
#include <memory>
2025-11-16 18:45:13 +11:00
#include <algorithm>
2025-11-15 13:52:23 +11:00
2025-11-10 20:44:10 +11:00
#include "../libs/linenoise.hpp"
enum class ValueTypes {
2025-11-16 18:45:13 +11:00
Identifier, Int, Double, String, List, Function, Type, Nil, Auto
2025-11-10 20:44:10 +11:00
};
enum class ParserErrorType {
UnexpectedEndOfInput,
UnexpectedToken,
MissingOpenParen,
MissingCloseParen,
UnexpectedTopLevelToken
};
enum class InterpreterErrorType {
UnknownInstruction,
UnexpectedToken,
IncorrectTokenType,
MathError,
UnknownEnvironment
};
2025-11-16 18:45:13 +11:00
enum class CTranspilerErrorType {
UnknownInstruction,
UnexpectedToken,
IncorrectTokenType,
UnknownEnvironment
};
class CTranspilerError : public std::exception {
std::string message;
CTranspilerErrorType type;
public:
explicit CTranspilerError(CTranspilerErrorType type) : type(type) {
std::stringstream ss;
ss << "Compiling error: ";
switch (type) {
case CTranspilerErrorType::UnknownInstruction:
ss << "UnknownInstruction";
break;
case CTranspilerErrorType::UnexpectedToken:
ss << "UnexpectedToken";
break;
case CTranspilerErrorType::IncorrectTokenType:
ss << "IncorrectTokenType";
break;
case CTranspilerErrorType::UnknownEnvironment:
ss << "UnknownEnvironment";
break;
}
message = ss.str();
}
[[nodiscard]] const char *what() const noexcept override {
return message.c_str();
}
};
2025-11-10 20:44:10 +11:00
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; }
};
2025-11-15 13:52:23 +11:00
class Instruction;
class Function {
public:
ValueTypes returnType = ValueTypes::Nil;
std::vector<std::pair<std::string, ValueTypes>> arguments;
std::vector<Instruction> code;
Function(ValueTypes returnType, std::vector<std::pair<std::string, ValueTypes>> args, std::vector<Instruction> code) : returnType(returnType), arguments(std::move(args)), code(std::move(code)) {}
Function() = default;
};
2025-11-10 20:44:10 +11:00
class Value {
2025-11-15 13:52:23 +11:00
std::variant<int, double, std::string, void*, std::vector<Value>, Function, ValueTypes> value;
2025-11-10 20:44:10 +11:00
public:
ValueTypes type;
2025-11-16 18:45:13 +11:00
[[nodiscard]] std::optional<int> getInt() const {
2025-11-10 20:44:10 +11:00
if (std::holds_alternative<int>(value)) {
return std::get<int>(value);
}
return {};
}
2025-11-16 18:45:13 +11:00
[[nodiscard]] std::optional<double> getDouble() const {
2025-11-10 20:44:10 +11:00
if (std::holds_alternative<double>(value)) {
return std::get<double>(value);
}
return {};
}
2025-11-16 18:45:13 +11:00
[[nodiscard]] std::optional<std::string> getString() const {
2025-11-10 20:44:10 +11:00
if (std::holds_alternative<std::string>(value)) {
return std::get<std::string>(value);
}
return {};
}
2025-11-16 18:45:13 +11:00
[[nodiscard]] std::optional<std::vector<Value>> getList() const {
2025-11-10 20:44:10 +11:00
if (std::holds_alternative<std::vector<Value>>(value)) {
return std::get<std::vector<Value>>(value);
}
return {};
}
2025-11-16 18:45:13 +11:00
[[nodiscard]] std::optional<Function> getFunction() const {
2025-11-15 13:52:23 +11:00
if (std::holds_alternative<Function>(value)) {
return std::get<Function>(value);
}
return {};
}
2025-11-16 18:45:13 +11:00
[[nodiscard]] std::optional<ValueTypes> getType() const {
2025-11-15 13:52:23 +11:00
if (std::holds_alternative<ValueTypes>(value)) {
return std::get<ValueTypes>(value);
}
return {};
}
2025-11-16 18:45:13 +11:00
void print() const {
2025-11-10 20:44:10 +11:00
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();
}
2025-11-16 18:45:13 +11:00
break;
2025-11-10 20:44:10 +11:00
}
2025-11-15 13:52:23 +11:00
case ValueTypes::Function: {
std::cout << "<function>";
break;
}
case ValueTypes::Type: {
std::cout << "<type>";
break;
}
default:
2025-11-10 20:44:10 +11:00
case ValueTypes::Nil: {
std::cout << "\033[2;3;96m" << "nil" << "\033[0m";
}
}
}
2025-11-16 18:45:13 +11:00
bool operator==(Value &otherValue) const {
2025-11-10 20:44:10 +11:00
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;
}
}
2025-11-16 18:45:13 +11:00
friend std::ostream& operator<<(std::ostream &os, const Value &obj) {
switch (obj.type) {
case ValueTypes::String: {
os << obj.getString().value();
break;
}
case ValueTypes::Int: {
os << obj.getInt().value();
break;
}
case ValueTypes::Double: {
os << obj.getDouble().value();
break;
}
case ValueTypes::List: {
auto list = obj.getList().value();
bool first = true;
for (auto& listElement : list) {
if (!first) {
os << ", ";
} else {
first = false;
}
listElement.print();
}
break;
}
case ValueTypes::Function: {
os << "<function>";
break;
}
case ValueTypes::Type: {
os << "<type>";
break;
}
default:
case ValueTypes::Nil: {
os << "\033[2;3;96m" << "nil" << "\033[0m";
}
}
return os;
}
2025-11-10 20:44:10 +11:00
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<Value> in) : value(in), type(ValueTypes::List) {}
2025-11-15 13:52:23 +11:00
explicit Value(Function in) : value(in), type(ValueTypes::Function) {}
explicit Value(ValueTypes in) : value(in), type(ValueTypes::Type) {}
2025-11-10 20:44:10 +11:00
Value(std::string in, bool isIdentifier) : value(in), type(isIdentifier ? ValueTypes::Identifier : ValueTypes::String) {}
};
class Instruction {
public:
std::string instruction;
std::vector<std::variant<Instruction, Value>> args;
Instruction(std::string instruction, std::vector<std::variant<Instruction, Value>> args) : instruction(std::move(instruction)), args(std::move(args)) {}
Instruction() = default;
};
class Parser {
std::string input;
std::vector<std::string> split;
int currentChar = -1;
int current = -1;
std::optional<char> consumeChar() {
currentChar++;
if (currentChar >= input.size()) {
return {};
}
return input[currentChar];
}
std::optional<std::string> consume() {
current++;
if (current >= split.size()) {
return {};
}
return split[current];
}
[[nodiscard]] std::optional<std::string> 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();
}
2025-11-15 13:52:23 +11:00
// Also check the types
if (token == "int") {
return Value(ValueTypes::Int);
}
if (token == "double") {
return Value(ValueTypes::Double);
}
if (token == "string") {
return Value(ValueTypes::String);
}
if (token == "list") {
return Value(ValueTypes::List);
}
if (token == "function") {
return Value(ValueTypes::Function);
}
if (token == "type") {
return Value(ValueTypes::Type);
}
2025-11-10 20:44:10 +11:00
// 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<std::variant<Instruction, Value>> 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<Instruction> 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) {
2025-11-15 13:52:23 +11:00
case '(': case ')': case '[': case ']': {
2025-11-10 20:44:10 +11:00
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<Instruction> instructions = {};
2025-11-15 13:52:23 +11:00
Value returnValue;
2025-11-10 20:44:10 +11:00
std::map<std::string, Value> environment = {{"pippleVersion", Value("0.0.1")}};
Value interpretInstruction(Instruction& instruction) {
// Preprocess identifiers
for (auto& arg : instruction.args) {
2025-11-15 13:52:23 +11:00
if (std::holds_alternative<Value>(arg) && instruction.instruction != "let" && instruction.instruction != "set" && instruction.instruction != "function") {
2025-11-10 20:44:10 +11:00
if (std::get<Value>(arg).type == ValueTypes::Identifier) {
std::string id = std::get<Value>(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>(instruction.args[0])) {
toContinue = interpretInstruction(std::get<Instruction>(instruction.args[0])).type != ValueTypes::Nil;
}
if (std::holds_alternative<Value>(instruction.args[0])) {
toContinue = std::get<Value>(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<Instruction>(arg)) {
returnValue = interpretInstruction(std::get<Instruction>(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>(instruction.args[0])) {
Instruction condCopy = std::get<Instruction>(instruction.args[0]);
conditionResult = interpretInstruction(condCopy);
} else if (std::holds_alternative<Value>(instruction.args[0])) {
conditionResult = std::get<Value>(instruction.args[0]);
}
if (conditionResult.type == ValueTypes::Nil) {
break;
}
for (size_t i = 1; i < instruction.args.size(); i++) {
if (std::holds_alternative<Instruction>(instruction.args[i])) {
Instruction bodyCopy = std::get<Instruction>(instruction.args[i]);
if (bodyCopy.instruction == "break") {
return Value();
}
interpretInstruction(bodyCopy);
}
}
}
return Value();
}
2025-11-15 13:52:23 +11:00
if (instruction.instruction == "function") {
// (function returntype [valtype argname valtype argname] (code) (morecode))
// returnType
if (std::holds_alternative<Instruction>(instruction.args[0])) {
instruction.args[0] = interpretInstruction(std::get<Instruction>(instruction.args[0]));
}
auto returnTypeValue = std::get<Value>(instruction.args[0]);
if (returnTypeValue.type != ValueTypes::Type) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
ValueTypes returnType = returnTypeValue.getType().value();
std::vector<std::pair<std::string, ValueTypes>> args;
// [valtype argname valtype argname]
int argAmount = 0;
if (std::holds_alternative<Value>(instruction.args[1]) && std::get<Value>(instruction.args[1]).type == ValueTypes::Identifier && std::get<Value>(instruction.args[1]).getString().value() != "[") {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
for (int i = 2; i < instruction.args.size(); i += 2) {
if (std::holds_alternative<Instruction>(instruction.args[i])) {
instruction.args[i] = interpretInstruction(std::get<Instruction>(instruction.args[i]));
}
// this is safe to do because we just evaluated the instruction
Value argTypeVal = std::get<Value>(instruction.args[i]);
if (argTypeVal.type != ValueTypes::Type) {
if (argTypeVal.type == ValueTypes::Identifier && argTypeVal.getString().value() == "]") {
break;
}
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
ValueTypes argType = argTypeVal.getType().value();
// identifiers cannot be returned by anything
if (std::holds_alternative<Instruction>(instruction.args[i + 1])) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
Value argNameVal = std::get<Value>(instruction.args[i + 1]);
if (argNameVal.type != ValueTypes::Identifier) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
std::string argName = argNameVal.getString().value();
args.emplace_back(argName, argType);
argAmount += 2;
}
// (code) (morecode)
std::vector<Instruction> body;
for (int i = argAmount + 3; i < instruction.args.size(); i++) {
if (std::holds_alternative<Instruction>(instruction.args[i])) {
body.push_back(std::get<Instruction>(instruction.args[i]));
} else {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
}
return Value(Function(returnType, args, body));
}
2025-11-10 20:44:10 +11:00
// Preprocess instructions inside instructions
for (auto &arg : instruction.args) {
if (std::holds_alternative<Instruction>(arg)) {
arg = interpretInstruction(std::get<Instruction>(arg));
}
}
// Instructions that don't require preprocessing go below this line
// It is safe to use std::get<Value>(...) on all arguments
2025-11-15 13:52:23 +11:00
// First, search for custom functions
if (environment.contains(instruction.instruction)) {
if (environment[instruction.instruction].type == ValueTypes::Function) {
Function function = environment[instruction.instruction].getFunction().value();
if (function.arguments.size() != instruction.args.size()) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
Interpreter fnInterpreter;
fnInterpreter.environment = this->environment;
for (int i = 0; i < instruction.args.size(); i++) {
Value argTypeVal = std::get<Value>(instruction.args[i]);
if (argTypeVal.type != function.arguments[i].second) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
fnInterpreter.environment[function.arguments[i].first] = std::get<Value>(instruction.args[i]);
}
Value fnReturnValue;
for (Instruction& inst : function.code) {
if (inst.instruction == "return") {
return fnInterpreter.interpretInstruction(inst);
}
fnReturnValue = fnInterpreter.interpretInstruction(inst);
}
return fnReturnValue;
} else {
return environment[instruction.instruction];
}
return Value();
}
2025-11-10 20:44:10 +11:00
if (instruction.instruction == "+" || instruction.instruction == "add") {
double result = 0;
for (const auto& arg : instruction.args) {
auto value = std::get<Value>(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<int>(result)) {
return Value(static_cast<int>(result));
}
return Value(result);
}
if (instruction.instruction == "-" || instruction.instruction == "subtract") {
if (instruction.args.empty()) return Value(0);
auto firstValue = std::get<Value>(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<Value>(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<int>(result)) {
return Value(static_cast<int>(result));
}
return Value(result);
}
if (instruction.instruction == "*" || instruction.instruction == "multiply") {
if (instruction.args.empty()) return Value(1);
auto firstValue = std::get<Value>(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<Value>(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<int>(result)) {
return Value(static_cast<int>(result));
}
return Value(result);
}
if (instruction.instruction == "/" || instruction.instruction == "divide") {
if (instruction.args.empty()) return Value(1);
auto firstValue = std::get<Value>(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<Value>(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<int>(result)) {
return Value(static_cast<int>(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<Value>(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<Value>(arg);
if (first) {
first = false;
firstVal = value;
} else {
if (value == firstVal) {
return Value();
}
}
}
return Value(1);
}
2025-11-15 13:52:23 +11:00
if (instruction.instruction == "<" || instruction.instruction == "lesser") {
if (instruction.args.size() != 2) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
auto left = std::get<Value>(instruction.args[0]);
auto right = std::get<Value>(instruction.args[1]);
double leftVal = 0, rightVal = 0;
if (left.type == ValueTypes::Int) {
leftVal = left.getInt().value();
} else if (left.type == ValueTypes::Double) {
leftVal = left.getDouble().value();
} else {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
if (right.type == ValueTypes::Int) {
rightVal = right.getInt().value();
} else if (right.type == ValueTypes::Double) {
rightVal = right.getDouble().value();
} else {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
return leftVal < rightVal ? Value(1) : Value();
}
if (instruction.instruction == ">" || instruction.instruction == "greater") {
if (instruction.args.size() != 2) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
auto left = std::get<Value>(instruction.args[0]);
auto right = std::get<Value>(instruction.args[1]);
double leftVal = 0, rightVal = 0;
if (left.type == ValueTypes::Int) {
leftVal = left.getInt().value();
} else if (left.type == ValueTypes::Double) {
leftVal = left.getDouble().value();
} else {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
if (right.type == ValueTypes::Int) {
rightVal = right.getInt().value();
} else if (right.type == ValueTypes::Double) {
rightVal = right.getDouble().value();
} else {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
return leftVal > rightVal ? Value(1) : Value();
}
2025-11-10 20:44:10 +11:00
if (instruction.instruction == "input") {
std::string input;
linenoise::Readline("", input);
return Value(input);
}
2025-11-15 13:52:23 +11:00
if (instruction.instruction == "return") {
if (instruction.args.empty()) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
return std::get<Value>(instruction.args[0]);
}
if (instruction.instruction == "list") {
std::vector<Value> list;
for (const auto& item : instruction.args) {
list.push_back(std::get<Value>(item));
}
return Value(list);
}
2025-11-10 20:44:10 +11:00
if (instruction.instruction == "let") {
if (instruction.args.size() < 2) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
auto idval = std::get<Value>(instruction.args[0]);
std::string id;
if (idval.type == ValueTypes::Identifier) {
id = idval.getString().value();
} else {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
environment[id] = std::get<Value>(instruction.args[1]);
return {environment[id]};
}
if (instruction.instruction == "set") {
if (instruction.args.size() < 2) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
auto idval = std::get<Value>(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<Value>(instruction.args[1]);
return {environment[id]};
}
if (instruction.instruction == "print") {
for (const auto& arg : instruction.args) {
auto value = std::get<Value>(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<Value>(arg);
value.print();
std::cout << " ";
}
std::cout << "\033[0m\n";
return Value();
}
if (instruction.instruction == "exit") {
if (!instruction.args.empty()) {
auto value = std::get<Value>(instruction.args[0]);
if (value.type == ValueTypes::Int) {
exit(value.getInt().value());
}
}
exit(0);
}
throw InterpretingError(InterpreterErrorType::UnknownInstruction);
}
explicit Interpreter(std::vector<Instruction> in) : instructions(std::move(in)) {
for (Instruction &instruction : instructions) {
2025-11-15 13:52:23 +11:00
if (instruction.instruction == "return") {
if (instruction.args.empty()) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
if (std::holds_alternative<Instruction>(instruction.args[0])) {
instruction.args[0] = interpretInstruction(std::get<Instruction>(instruction.args[0]));
}
returnValue = std::get<Value>(instruction.args[0]);
return;
}
returnValue = interpretInstruction(instruction);
2025-11-10 20:44:10 +11:00
}
}
Interpreter() = default;
};
2025-11-16 18:45:13 +11:00
class CppTranspiler {
std::vector<Instruction> instructions;
std::map<std::string, Function> functions;
std::map<std::string, Value> environment;
std::vector<std::string> headers;
std::vector<std::string> body;
std::vector<std::string> tail;
int fnAmount = 0;
void validateFunctionCall(const std::string& name, const std::vector<std::variant<Value, std::string>>& args) {
// Check if function exists
if (functions.find(name) == functions.end()) {
throw CTranspilerError(CTranspilerErrorType::UnknownEnvironment);
}
Function& func = functions[name];
// Check argument count
if (args.size() != func.arguments.size()) {
std::cerr << "Function '" << name << "' expects "
<< func.arguments.size() << " arguments but got "
<< args.size() << std::endl;
throw CTranspilerError(CTranspilerErrorType::IncorrectTokenType);
}
// Check argument types (if we know them at compile time)
for (size_t i = 0; i < args.size(); i++) {
if (std::holds_alternative<Value>(args[i])) {
Value argVal = std::get<Value>(args[i]);
ValueTypes expectedType = func.arguments[i].second;
// Only validate if it's a literal value (not an identifier or expression)
if (argVal.type != ValueTypes::Identifier && argVal.type != expectedType) {
std::cerr << "Function '" << name << "' argument " << i
<< " ('" << func.arguments[i].first << "') expects type "
<< static_cast<int>(expectedType) << " but got "
<< static_cast<int>(argVal.type) << std::endl;
throw CTranspilerError(CTranspilerErrorType::IncorrectTokenType);
}
}
}
}
std::string compileFunction(const Instruction &instruction) {
std::stringstream functionss;
Function function;
auto returnTypeValue = std::get<Value>(instruction.args[0]);
if (returnTypeValue.type != ValueTypes::Type) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
ValueTypes returnType = returnTypeValue.getType().value();
std::vector<std::pair<std::string, ValueTypes>> args;
int argAmount = 0;
if (std::holds_alternative<Value>(instruction.args[1]) &&
std::get<Value>(instruction.args[1]).type == ValueTypes::Identifier &&
std::get<Value>(instruction.args[1]).getString().value() != "[") {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
// Parse arguments
for (int i = 2; i < instruction.args.size(); i += 2) {
Value argTypeVal = std::get<Value>(instruction.args[i]);
if (argTypeVal.type != ValueTypes::Type) {
if (argTypeVal.type == ValueTypes::Identifier &&
argTypeVal.getString().value() == "]") {
break;
}
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
ValueTypes argType = argTypeVal.getType().value();
if (std::holds_alternative<Instruction>(instruction.args[i + 1])) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
Value argNameVal = std::get<Value>(instruction.args[i + 1]);
if (argNameVal.type != ValueTypes::Identifier) {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
std::string argName = argNameVal.getString().value();
args.emplace_back(argName, argType);
argAmount += 2;
}
std::vector<Instruction> body;
for (int i = argAmount + 3; i < instruction.args.size(); i++) {
if (std::holds_alternative<Instruction>(instruction.args[i])) {
body.push_back(std::get<Instruction>(instruction.args[i]));
} else {
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
}
// Store in function object
function.returnType = returnType;
function.arguments = args;
function.code = body;
// Generate C++ code
std::string returntype;
switch (returnType) {
case ValueTypes::Int:
returntype = "int";
break;
case ValueTypes::Double:
returntype = "double";
break;
case ValueTypes::String:
returntype = "std::string";
break;
default:
std::cout << "that's not currently supported" << std::endl;
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
bool first = true;
std::stringstream argss;
for (const auto& arg : function.arguments) {
if (!first) {
argss << ", ";
}
first = false;
switch (arg.second) {
case ValueTypes::Int:
argss << "int " << arg.first;
break;
case ValueTypes::Double:
argss << "double " << arg.first;
break;
case ValueTypes::String:
argss << "std::string " << arg.first;
break;
default:
std::cout << "that's not currently supported" << std::endl;
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
}
}
functionss << "[](" << argss.str() << ") -> " << returntype << " {\n";
for (const auto& inst : function.code) {
functionss << " " << compileInstruction(inst) << ";\n";
}
functionss << "}";
return functionss.str();
}
std::string compileInstruction(const Instruction &instruction) {
// the value is for values being held, the std::string is for compiled instructions
std::vector<std::variant<Value, std::string>> args;
for (const auto &arg : instruction.args) {
if (std::holds_alternative<Instruction>(arg)) {
args.emplace_back("(" + compileInstruction(std::get<Instruction>(arg)) + ")");
}
if (std::holds_alternative<Value>(arg)) {
args.emplace_back(std::get<Value>(arg));
}
}
if (functions.find(instruction.instruction) != functions.end()) {
// Validate the call
validateFunctionCall(instruction.instruction, args);
// Compile the call
std::stringstream call;
call << instruction.instruction << "(";
bool first = true;
for (const auto& arg : args) {
if (!first) call << ", ";
first = false;
if (std::holds_alternative<std::string>(arg)) {
call << std::get<std::string>(arg);
} else {
Value v = std::get<Value>(arg);
if (v.type == ValueTypes::String) {
call << "\"" << v.getString().value() << "\"";
} else if (v.type == ValueTypes::Int) {
call << v.getInt().value();
} else if (v.type == ValueTypes::Double) {
call << v.getDouble().value();
} else if (v.type == ValueTypes::Identifier) {
call << v.getString().value();
}
}
}
call << ")";
return call.str();
}
if (instruction.instruction == "let") {
if (args.size() < 2) {
throw CTranspilerError(CTranspilerErrorType::IncorrectTokenType);
}
if (!std::holds_alternative<Value>(args[0])) {
throw CTranspilerError(CTranspilerErrorType::IncorrectTokenType);
}
Value nameVal = std::get<Value>(args[0]);
if (nameVal.type != ValueTypes::Identifier) {
throw CTranspilerError(CTranspilerErrorType::IncorrectTokenType);
}
std::string varName = nameVal.getString().value();
// Check if it's a function definition
if (std::holds_alternative<std::string>(args[1])) {
std::string compiled = std::get<std::string>(args[1]);
// Check if the original instruction.args[1] was a function
if (std::holds_alternative<Instruction>(instruction.args[1])) {
Instruction funcInst = std::get<Instruction>(instruction.args[1]);
if (funcInst.instruction == "function") {
// Extract the function signature and store it
Function func;
auto returnTypeValue = std::get<Value>(funcInst.args[0]);
func.returnType = returnTypeValue.getType().value();
// Parse arguments
int argAmount = 0;
for (int i = 2; i < funcInst.args.size(); i += 2) {
Value argTypeVal = std::get<Value>(funcInst.args[i]);
if (argTypeVal.type != ValueTypes::Type) {
if (argTypeVal.type == ValueTypes::Identifier &&
argTypeVal.getString().value() == "]") {
break;
}
break;
}
ValueTypes argType = argTypeVal.getType().value();
Value argNameVal = std::get<Value>(funcInst.args[i + 1]);
std::string argName = argNameVal.getString().value();
func.arguments.emplace_back(argName, argType);
argAmount += 2;
}
// Store the function signature
functions[varName] = func;
}
}
return "auto " + varName + " = " + compiled;
}
std::stringstream code;
code << "auto ";
if (std::holds_alternative<Value>(args[0]) && std::get<Value>(args[0]).type == ValueTypes::Identifier) {
code << std::get<Value>(args[0]).getString().value() << " = ";
} else {
throw CTranspilerError(CTranspilerErrorType::IncorrectTokenType);
}
if (std::holds_alternative<Value>(args[1])) {
code << std::get<Value>(args[1]);
}
if (std::holds_alternative<std::string>(args[1])) {
code << std::get<std::string>(args[1]);
}
return code.str();
}
if (instruction.instruction == "function") {
if (std::ranges::find(headers, "functional") == headers.end()) {
headers.emplace_back("functional");
}
return compileFunction(instruction);
}
if (instruction.instruction == "return") {
if (!args.empty()) {
if (std::holds_alternative<Value>(args[0])) {
Value value = std::get<Value>(args[0]);
if (value.type == ValueTypes::Int) {
return "return " + std::to_string(value.getInt().value());
} else {
throw CTranspilerError(CTranspilerErrorType::IncorrectTokenType);
}
}
if (std::holds_alternative<std::string>(args[0])) {
return "return(" + std::get<std::string>(args[0]) + ")";
}
} else {
throw CTranspilerError(CTranspilerErrorType::IncorrectTokenType);
}
}
if (instruction.instruction == "print") {
std::stringstream retval;
if (std::ranges::find(headers, "iostream") == headers.end()) {
headers.emplace_back("iostream");
}
for (const auto &arg : args) {
if (std::holds_alternative<std::string>(arg)) {
retval << "std::cout << " << std::get<std::string>(arg) << " << \"\\n\"";
}
if (std::holds_alternative<Value>(arg)) {
if (std::get<Value>(arg).type == ValueTypes::String) {
retval << "std::cout << \"" << std::get<Value>(arg) << "\" << \"\\n\"";
} else {
retval << "std::cout << " << std::get<Value>(arg) << " << \"\\n\"";
}
}
}
return retval.str();
}
throw InterpretingError(InterpreterErrorType::UnknownInstruction);
}
public:
std::string createCppFile() const {
std::stringstream cpp;
cpp << "// Autogenerated by the Pipple compiler.\n// Headers\n";
for (const auto &header : headers) {
cpp << "#include <" << header << ">\n";
}
cpp << "\n// Code\n";
for (const auto &body : body) {
cpp << body << "\n";
}
for (const auto &tail : tail) {
cpp << tail << "\n";
}
return cpp.str();
}
void addInstruction(const Instruction &instruction) {
body.emplace_back(compileInstruction(instruction) + ";");
}
explicit CppTranspiler(std::vector<Instruction> in) : instructions(std::move(in)) {
for (Instruction &instruction : instructions) {
body.push_back(compileInstruction(instruction));
}
std::cout << createCppFile() << std::endl;
}
CppTranspiler() {
headers = {};
body = {
"void* nil = nullptr;",
"int main(int argc, char** argv) {",
};
tail = {
"}"
};
}
};
2025-11-10 20:44:10 +11:00
int main(int argc, char** argv) {
bool isInteractive = true;
2025-11-16 18:45:13 +11:00
bool compiling = false;
2025-11-10 20:44:10 +11:00
std::string program;
if (argc > 1) {
2025-11-16 18:45:13 +11:00
int fileNum = 1;
if (strcmp(argv[1], "-c") == 0) {
fileNum = 2;
compiling = true;
}
2025-11-10 20:44:10 +11:00
isInteractive = false;
2025-11-16 18:45:13 +11:00
std::ifstream ifs(argv[fileNum]);
2025-11-10 20:44:10 +11:00
std::stringstream buffer;
if (ifs.is_open()) {
buffer << ifs.rdbuf();
}
program = buffer.str();
}
linenoise::SetMultiLine(true);
linenoise::SetHistoryMaxLen(50);
Parser parser;
Interpreter interpreter;
2025-11-16 18:45:13 +11:00
CppTranspiler transpiler;
2025-11-10 20:44:10 +11:00
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<int>(i);
}
// Show context (30 chars before and after)
int start = std::max(0, charPos - 30);
int end = std::min(static_cast<int>(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<int>(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 {
2025-11-16 18:45:13 +11:00
if (compiling) {
for (const auto &instruction : parser.instructions) {
transpiler.addInstruction(instruction);
}
2025-11-10 20:44:10 +11:00
2025-11-16 18:45:13 +11:00
} else {
for (auto instruction : parser.instructions) {
interpreter.interpretInstruction(instruction);
}
}
2025-11-10 20:44:10 +11:00
} catch (const InterpretingError& e) {
std::cerr << e.what() << std::endl;
if (!isInteractive) return 1;
}
if (!isInteractive) {
break;
}
}
2025-11-16 18:45:13 +11:00
if (compiling) {
std::cout << transpiler.createCppFile() << std::endl;
}
2025-11-10 20:44:10 +11:00
return 0;
2025-11-15 13:52:23 +11:00
}