Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
build
|
||||||
|
cmake-build-debug
|
||||||
|
.idea
|
||||||
7
CMakeLists.txt
Normal file
7
CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cmake_minimum_required(VERSION 4.0)
|
||||||
|
project(pipple)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
||||||
|
|
||||||
|
add_executable(pipple src/main.cpp)
|
||||||
88
README.md
Normal file
88
README.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Pipple
|
||||||
|
|
||||||
|
Pipple is a simple Lisp-family programming language.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
Pipple builds with CMake. Run the following to build Pipple:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
The `pipple` executable will be placed in the build folder.
|
||||||
|
|
||||||
|
## Coding with Pipple
|
||||||
|
|
||||||
|
Pipple's interpreter provides a REPL for fast prototyping. Try it out by running the `pipple` command without any arguments.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./build/pipple
|
||||||
|
pipple>
|
||||||
|
```
|
||||||
|
|
||||||
|
Pipple code you write will be run now. Try something like `(print "Hello, World!")`. See what happens!
|
||||||
|
|
||||||
|
If you'd like to use Pipple with a file, just add the filename after the executable path.
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
Pipple's syntax looks like most other Lisps. You enclose your statements in `(` and `)`.
|
||||||
|
|
||||||
|
### Printing
|
||||||
|
|
||||||
|
```
|
||||||
|
(print "Hello, World!")
|
||||||
|
(print 32)
|
||||||
|
(print 3.141)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting and Changing Variables
|
||||||
|
|
||||||
|
```
|
||||||
|
(let x 3.141)
|
||||||
|
(print x)
|
||||||
|
(set x 2.712)
|
||||||
|
(print x)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Math
|
||||||
|
|
||||||
|
Remember: Polish notation is the key!
|
||||||
|
|
||||||
|
```
|
||||||
|
(let x (+ 3 4))
|
||||||
|
(print x)
|
||||||
|
|
||||||
|
(let y (* 5 6 2))
|
||||||
|
(print y)
|
||||||
|
|
||||||
|
(print (/ 10 2))
|
||||||
|
|
||||||
|
(print (- 10 5))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditionals
|
||||||
|
|
||||||
|
```
|
||||||
|
(let x 0)
|
||||||
|
(if (== x 0)
|
||||||
|
(print "x is zero")
|
||||||
|
(set x 1)
|
||||||
|
(print "now it is" x)
|
||||||
|
)
|
||||||
|
|
||||||
|
(while (!= x 5)
|
||||||
|
(print "x is currently" x "which is not 5. i must make it 5")
|
||||||
|
(set x (+ x 1))
|
||||||
|
)
|
||||||
|
(print "now x is" x)
|
||||||
|
```
|
||||||
|
|
||||||
|
### User console input
|
||||||
|
|
||||||
|
```
|
||||||
|
(let x (input))
|
||||||
|
(print "You said" x)
|
||||||
|
```
|
||||||
2587
libs/linenoise.hpp
Normal file
2587
libs/linenoise.hpp
Normal file
File diff suppressed because it is too large
Load Diff
790
src/main.cpp
Normal file
790
src/main.cpp
Normal file
@@ -0,0 +1,790 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
#include <cctype>
|
||||||
|
#include <complex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <map>
|
||||||
|
#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<int, double, std::string, void*, std::vector<Value>> value;
|
||||||
|
public:
|
||||||
|
ValueTypes type;
|
||||||
|
std::optional<int> getInt() {
|
||||||
|
if (std::holds_alternative<int>(value)) {
|
||||||
|
return std::get<int>(value);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::optional<double> getDouble() {
|
||||||
|
if (std::holds_alternative<double>(value)) {
|
||||||
|
return std::get<double>(value);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::optional<std::string> getString() {
|
||||||
|
if (std::holds_alternative<std::string>(value)) {
|
||||||
|
return std::get<std::string>(value);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::optional<std::vector<Value>> getList() {
|
||||||
|
if (std::holds_alternative<std::vector<Value>>(value)) {
|
||||||
|
return std::get<std::vector<Value>>(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<Value> 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<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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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<Instruction> instructions = {};
|
||||||
|
std::map<std::string, Value> environment = {{"pippleVersion", Value("0.0.1")}};
|
||||||
|
|
||||||
|
Value interpretInstruction(Instruction& instruction) {
|
||||||
|
// Preprocess identifiers
|
||||||
|
for (auto& arg : instruction.args) {
|
||||||
|
if (std::holds_alternative<Value>(arg) && instruction.instruction != "let" && instruction.instruction != "set") {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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<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) {
|
||||||
|
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<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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
9
tests/cheese.ppl
Normal file
9
tests/cheese.ppl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
(let in "no")
|
||||||
|
(while (!= in "yes")
|
||||||
|
(print "Do you like cheese?")
|
||||||
|
(set in (input))
|
||||||
|
(if (!= in "yes")
|
||||||
|
(print "That is sad")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(print "Awesome! I do too!")
|
||||||
11
tests/num.ppl
Normal file
11
tests/num.ppl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
(let x 0)
|
||||||
|
(if (== x 0)
|
||||||
|
(print "x is zero")
|
||||||
|
(set x 1)
|
||||||
|
(print "now it is" x)
|
||||||
|
)
|
||||||
|
|
||||||
|
(while (!= x 5)
|
||||||
|
(print "x is currently" x "which is not 5. i must make it 5")
|
||||||
|
(set x (+ x 1))
|
||||||
|
)
|
||||||
5
tests/to10000.ppl
Normal file
5
tests/to10000.ppl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
(let x 0)
|
||||||
|
(while (!= x 10000)
|
||||||
|
(set x (+ 1 x))
|
||||||
|
(print x)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user