Add functions and lists
This commit is contained in:
36
README.md
36
README.md
@@ -19,7 +19,7 @@ Pipple's interpreter provides a REPL for fast prototyping. Try it out by running
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ ./build/pipple
|
$ ./build/pipple
|
||||||
pipple>
|
pipple>
|
||||||
```
|
```
|
||||||
|
|
||||||
Pipple code you write will be run now. Try something like `(print "Hello, World!")`. See what happens!
|
Pipple code you write will be run now. Try something like `(print "Hello, World!")`. See what happens!
|
||||||
@@ -65,6 +65,8 @@ Remember: Polish notation is the key!
|
|||||||
|
|
||||||
### Conditionals
|
### Conditionals
|
||||||
|
|
||||||
|
In Pipple, `nil` is the only non-truthy value. Every non-`nil` value is truthy.
|
||||||
|
|
||||||
```
|
```
|
||||||
(let x 0)
|
(let x 0)
|
||||||
(if (== x 0)
|
(if (== x 0)
|
||||||
@@ -85,4 +87,34 @@ Remember: Polish notation is the key!
|
|||||||
```
|
```
|
||||||
(let x (input))
|
(let x (input))
|
||||||
(print "You said" x)
|
(print "You said" x)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Lists
|
||||||
|
|
||||||
|
```
|
||||||
|
(list item1 item2 item3)
|
||||||
|
(list "hi there" 32 nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
Pipple functions require type annotations. They are also first class.
|
||||||
|
|
||||||
|
Functions are defined with the following syntax:
|
||||||
|
|
||||||
|
`(function returntype [argtype argname argtype argname] (code) (morecode))`
|
||||||
|
|
||||||
|
```
|
||||||
|
(let x (function int [int x]
|
||||||
|
(return (* x 5))
|
||||||
|
))
|
||||||
|
|
||||||
|
(print (x 3))
|
||||||
|
```
|
||||||
|
|
||||||
|
Valid types in Pipple are:
|
||||||
|
|
||||||
|
* int
|
||||||
|
* double
|
||||||
|
* string
|
||||||
|
* list
|
||||||
|
|||||||
238
src/main.cpp
238
src/main.cpp
@@ -7,10 +7,12 @@
|
|||||||
#include <complex>
|
#include <complex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "../libs/linenoise.hpp"
|
#include "../libs/linenoise.hpp"
|
||||||
|
|
||||||
enum class ValueTypes {
|
enum class ValueTypes {
|
||||||
Identifier, Int, Double, String, List, Nil
|
Identifier, Int, Double, String, List, Function, Type, Nil
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ParserErrorType {
|
enum class ParserErrorType {
|
||||||
@@ -104,8 +106,19 @@ public:
|
|||||||
[[nodiscard]] const std::string& getSourceCode() const { return sourceCode; }
|
[[nodiscard]] const std::string& getSourceCode() const { return sourceCode; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
class Value {
|
class Value {
|
||||||
std::variant<int, double, std::string, void*, std::vector<Value>> value;
|
std::variant<int, double, std::string, void*, std::vector<Value>, Function, ValueTypes> value;
|
||||||
public:
|
public:
|
||||||
ValueTypes type;
|
ValueTypes type;
|
||||||
std::optional<int> getInt() {
|
std::optional<int> getInt() {
|
||||||
@@ -132,6 +145,18 @@ class Value {
|
|||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
std::optional<Function> getFunction() {
|
||||||
|
if (std::holds_alternative<Function>(value)) {
|
||||||
|
return std::get<Function>(value);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::optional<ValueTypes> getType() {
|
||||||
|
if (std::holds_alternative<ValueTypes>(value)) {
|
||||||
|
return std::get<ValueTypes>(value);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
void print() {
|
void print() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ValueTypes::String: {
|
case ValueTypes::String: {
|
||||||
@@ -158,6 +183,15 @@ class Value {
|
|||||||
listElement.print();
|
listElement.print();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case ValueTypes::Function: {
|
||||||
|
std::cout << "<function>";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ValueTypes::Type: {
|
||||||
|
std::cout << "<type>";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
case ValueTypes::Nil: {
|
case ValueTypes::Nil: {
|
||||||
std::cout << "\033[2;3;96m" << "nil" << "\033[0m";
|
std::cout << "\033[2;3;96m" << "nil" << "\033[0m";
|
||||||
}
|
}
|
||||||
@@ -211,6 +245,8 @@ class Value {
|
|||||||
explicit Value(double in) : value(in), type(ValueTypes::Double) {}
|
explicit Value(double in) : value(in), type(ValueTypes::Double) {}
|
||||||
explicit Value(std::string in) : value(in), type(ValueTypes::String) {}
|
explicit Value(std::string in) : value(in), type(ValueTypes::String) {}
|
||||||
explicit Value(std::vector<Value> in) : value(in), type(ValueTypes::List) {}
|
explicit Value(std::vector<Value> in) : value(in), type(ValueTypes::List) {}
|
||||||
|
explicit Value(Function in) : value(in), type(ValueTypes::Function) {}
|
||||||
|
explicit Value(ValueTypes in) : value(in), type(ValueTypes::Type) {}
|
||||||
Value(std::string in, bool isIdentifier) : value(in), type(isIdentifier ? ValueTypes::Identifier : ValueTypes::String) {}
|
Value(std::string in, bool isIdentifier) : value(in), type(isIdentifier ? ValueTypes::Identifier : ValueTypes::String) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -258,6 +294,26 @@ class Parser {
|
|||||||
return Value();
|
return Value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if it's a string literal
|
// Check if it's a string literal
|
||||||
if (token.front() == '"' && token.back() == '"') {
|
if (token.front() == '"' && token.back() == '"') {
|
||||||
return Value(token.substr(1, token.length() - 2));
|
return Value(token.substr(1, token.length() - 2));
|
||||||
@@ -350,7 +406,7 @@ class Parser {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '(': case ')': {
|
case '(': case ')': case '[': case ']': {
|
||||||
if (!buf.empty()) {
|
if (!buf.empty()) {
|
||||||
split.push_back(buf);
|
split.push_back(buf);
|
||||||
buf.clear();
|
buf.clear();
|
||||||
@@ -395,12 +451,13 @@ class Parser {
|
|||||||
class Interpreter {
|
class Interpreter {
|
||||||
public:
|
public:
|
||||||
std::vector<Instruction> instructions = {};
|
std::vector<Instruction> instructions = {};
|
||||||
|
Value returnValue;
|
||||||
std::map<std::string, Value> environment = {{"pippleVersion", Value("0.0.1")}};
|
std::map<std::string, Value> environment = {{"pippleVersion", Value("0.0.1")}};
|
||||||
|
|
||||||
Value interpretInstruction(Instruction& instruction) {
|
Value interpretInstruction(Instruction& instruction) {
|
||||||
// Preprocess identifiers
|
// Preprocess identifiers
|
||||||
for (auto& arg : instruction.args) {
|
for (auto& arg : instruction.args) {
|
||||||
if (std::holds_alternative<Value>(arg) && instruction.instruction != "let" && instruction.instruction != "set") {
|
if (std::holds_alternative<Value>(arg) && instruction.instruction != "let" && instruction.instruction != "set" && instruction.instruction != "function") {
|
||||||
if (std::get<Value>(arg).type == ValueTypes::Identifier) {
|
if (std::get<Value>(arg).type == ValueTypes::Identifier) {
|
||||||
std::string id = std::get<Value>(arg).getString().value();
|
std::string id = std::get<Value>(arg).getString().value();
|
||||||
if (!environment.contains(id)) {
|
if (!environment.contains(id)) {
|
||||||
@@ -468,6 +525,64 @@ class Interpreter {
|
|||||||
}
|
}
|
||||||
return Value();
|
return Value();
|
||||||
}
|
}
|
||||||
|
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));
|
||||||
|
}
|
||||||
// Preprocess instructions inside instructions
|
// Preprocess instructions inside instructions
|
||||||
for (auto &arg : instruction.args) {
|
for (auto &arg : instruction.args) {
|
||||||
if (std::holds_alternative<Instruction>(arg)) {
|
if (std::holds_alternative<Instruction>(arg)) {
|
||||||
@@ -476,6 +591,37 @@ class Interpreter {
|
|||||||
}
|
}
|
||||||
// Instructions that don't require preprocessing go below this line
|
// Instructions that don't require preprocessing go below this line
|
||||||
// It is safe to use std::get<Value>(...) on all arguments
|
// It is safe to use std::get<Value>(...) on all arguments
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
if (instruction.instruction == "+" || instruction.instruction == "add") {
|
if (instruction.instruction == "+" || instruction.instruction == "add") {
|
||||||
double result = 0;
|
double result = 0;
|
||||||
for (const auto& arg : instruction.args) {
|
for (const auto& arg : instruction.args) {
|
||||||
@@ -629,11 +775,81 @@ class Interpreter {
|
|||||||
}
|
}
|
||||||
return Value(1);
|
return Value(1);
|
||||||
}
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
if (instruction.instruction == "input") {
|
if (instruction.instruction == "input") {
|
||||||
std::string input;
|
std::string input;
|
||||||
linenoise::Readline("", input);
|
linenoise::Readline("", input);
|
||||||
return Value(input);
|
return Value(input);
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
if (instruction.instruction == "let") {
|
if (instruction.instruction == "let") {
|
||||||
if (instruction.args.size() < 2) {
|
if (instruction.args.size() < 2) {
|
||||||
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
|
throw InterpretingError(InterpreterErrorType::IncorrectTokenType);
|
||||||
@@ -697,7 +913,17 @@ class Interpreter {
|
|||||||
}
|
}
|
||||||
explicit Interpreter(std::vector<Instruction> in) : instructions(std::move(in)) {
|
explicit Interpreter(std::vector<Instruction> in) : instructions(std::move(in)) {
|
||||||
for (Instruction &instruction : instructions) {
|
for (Instruction &instruction : instructions) {
|
||||||
interpretInstruction(instruction);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Interpreter() = default;
|
Interpreter() = default;
|
||||||
@@ -787,4 +1013,4 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
11
tests/fibonacci.ppl
Normal file
11
tests/fibonacci.ppl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
(let fib (function int [int y function fib]
|
||||||
|
(let retval 0)
|
||||||
|
(if (== 1 y) (set retval 0))
|
||||||
|
(if (== 2 y) (set retval 1))
|
||||||
|
(if (> y 2)
|
||||||
|
(set retval (+ (fib (- y 1) fib) (fib (- y 2) fib)))
|
||||||
|
)
|
||||||
|
(return retval)
|
||||||
|
))
|
||||||
|
|
||||||
|
(print (fib 30 fib))
|
||||||
6
tests/functions.ppl
Normal file
6
tests/functions.ppl
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
(let x (function int [function in int num]
|
||||||
|
(print "recursively called" (+ num 1) "times")
|
||||||
|
(in in (+ num 1))
|
||||||
|
))
|
||||||
|
|
||||||
|
(x x 1)
|
||||||
Reference in New Issue
Block a user