From c9c00e219d3d77b7dba9b0aadf797d32d891cd1a Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Sat, 15 Nov 2025 13:52:23 +1100 Subject: [PATCH] Add functions and lists --- README.md | 36 ++++++- src/main.cpp | 238 ++++++++++++++++++++++++++++++++++++++++++-- tests/fibonacci.ppl | 11 ++ tests/functions.ppl | 6 ++ 4 files changed, 283 insertions(+), 8 deletions(-) create mode 100644 tests/fibonacci.ppl create mode 100644 tests/functions.ppl diff --git a/README.md b/README.md index 1328d30..491c046 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Pipple's interpreter provides a REPL for fast prototyping. Try it out by running ``` $ ./build/pipple -pipple> +pipple> ``` 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 +In Pipple, `nil` is the only non-truthy value. Every non-`nil` value is truthy. + ``` (let x 0) (if (== x 0) @@ -85,4 +87,34 @@ Remember: Polish notation is the key! ``` (let x (input)) (print "You said" x) -``` \ No newline at end of file +``` + +### 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 diff --git a/src/main.cpp b/src/main.cpp index 6459dda..7c418bf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,10 +7,12 @@ #include #include #include +#include + #include "../libs/linenoise.hpp" enum class ValueTypes { - Identifier, Int, Double, String, List, Nil + Identifier, Int, Double, String, List, Function, Type, Nil }; enum class ParserErrorType { @@ -104,8 +106,19 @@ public: [[nodiscard]] const std::string& getSourceCode() const { return sourceCode; } }; +class Instruction; + +class Function { + public: + ValueTypes returnType = ValueTypes::Nil; + std::vector> arguments; + std::vector code; + Function(ValueTypes returnType, std::vector> args, std::vector code) : returnType(returnType), arguments(std::move(args)), code(std::move(code)) {} + Function() = default; +}; + class Value { - std::variant> value; + std::variant, Function, ValueTypes> value; public: ValueTypes type; std::optional getInt() { @@ -132,6 +145,18 @@ class Value { } return {}; } + std::optional getFunction() { + if (std::holds_alternative(value)) { + return std::get(value); + } + return {}; + } + std::optional getType() { + if (std::holds_alternative(value)) { + return std::get(value); + } + return {}; + } void print() { switch (type) { case ValueTypes::String: { @@ -158,6 +183,15 @@ class Value { listElement.print(); } } + case ValueTypes::Function: { + std::cout << ""; + break; + } + case ValueTypes::Type: { + std::cout << ""; + break; + } + default: case ValueTypes::Nil: { 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(std::string in) : value(in), type(ValueTypes::String) {} explicit Value(std::vector 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) {} }; @@ -258,6 +294,26 @@ class Parser { 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 if (token.front() == '"' && token.back() == '"') { return Value(token.substr(1, token.length() - 2)); @@ -350,7 +406,7 @@ class Parser { continue; } switch (c) { - case '(': case ')': { + case '(': case ')': case '[': case ']': { if (!buf.empty()) { split.push_back(buf); buf.clear(); @@ -395,12 +451,13 @@ class Parser { class Interpreter { public: std::vector instructions = {}; + Value returnValue; 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::holds_alternative(arg) && instruction.instruction != "let" && instruction.instruction != "set" && instruction.instruction != "function") { if (std::get(arg).type == ValueTypes::Identifier) { std::string id = std::get(arg).getString().value(); if (!environment.contains(id)) { @@ -468,6 +525,64 @@ class Interpreter { } return Value(); } + if (instruction.instruction == "function") { + // (function returntype [valtype argname valtype argname] (code) (morecode)) + + // returnType + if (std::holds_alternative(instruction.args[0])) { + instruction.args[0] = interpretInstruction(std::get(instruction.args[0])); + } + auto returnTypeValue = std::get(instruction.args[0]); + if (returnTypeValue.type != ValueTypes::Type) { + throw InterpretingError(InterpreterErrorType::IncorrectTokenType); + } + ValueTypes returnType = returnTypeValue.getType().value(); + std::vector> args; + + // [valtype argname valtype argname] + int argAmount = 0; + if (std::holds_alternative(instruction.args[1]) && std::get(instruction.args[1]).type == ValueTypes::Identifier && std::get(instruction.args[1]).getString().value() != "[") { + throw InterpretingError(InterpreterErrorType::IncorrectTokenType); + } + for (int i = 2; i < instruction.args.size(); i += 2) { + if (std::holds_alternative(instruction.args[i])) { + instruction.args[i] = interpretInstruction(std::get(instruction.args[i])); + } + // this is safe to do because we just evaluated the instruction + Value argTypeVal = std::get(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.args[i + 1])) { + throw InterpretingError(InterpreterErrorType::IncorrectTokenType); + } + Value argNameVal = std::get(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 body; + for (int i = argAmount + 3; i < instruction.args.size(); i++) { + if (std::holds_alternative(instruction.args[i])) { + body.push_back(std::get(instruction.args[i])); + } else { + throw InterpretingError(InterpreterErrorType::IncorrectTokenType); + } + } + return Value(Function(returnType, args, body)); + } // Preprocess instructions inside instructions for (auto &arg : instruction.args) { if (std::holds_alternative(arg)) { @@ -476,6 +591,37 @@ class Interpreter { } // Instructions that don't require preprocessing go below this line // It is safe to use std::get(...) 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(instruction.args[i]); + if (argTypeVal.type != function.arguments[i].second) { + throw InterpretingError(InterpreterErrorType::IncorrectTokenType); + } + fnInterpreter.environment[function.arguments[i].first] = std::get(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") { double result = 0; for (const auto& arg : instruction.args) { @@ -629,11 +775,81 @@ class Interpreter { } return Value(1); } + if (instruction.instruction == "<" || instruction.instruction == "lesser") { + if (instruction.args.size() != 2) { + throw InterpretingError(InterpreterErrorType::IncorrectTokenType); + } + + auto left = std::get(instruction.args[0]); + auto right = std::get(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(instruction.args[0]); + auto right = std::get(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") { std::string input; linenoise::Readline("", input); return Value(input); } + if (instruction.instruction == "return") { + if (instruction.args.empty()) { + throw InterpretingError(InterpreterErrorType::IncorrectTokenType); + } + return std::get(instruction.args[0]); + } + if (instruction.instruction == "list") { + std::vector list; + for (const auto& item : instruction.args) { + list.push_back(std::get(item)); + } + return Value(list); + } if (instruction.instruction == "let") { if (instruction.args.size() < 2) { throw InterpretingError(InterpreterErrorType::IncorrectTokenType); @@ -697,7 +913,17 @@ class Interpreter { } explicit Interpreter(std::vector in) : instructions(std::move(in)) { for (Instruction &instruction : instructions) { - interpretInstruction(instruction); + if (instruction.instruction == "return") { + if (instruction.args.empty()) { + throw InterpretingError(InterpreterErrorType::IncorrectTokenType); + } + if (std::holds_alternative(instruction.args[0])) { + instruction.args[0] = interpretInstruction(std::get(instruction.args[0])); + } + returnValue = std::get(instruction.args[0]); + return; + } + returnValue = interpretInstruction(instruction); } } Interpreter() = default; @@ -787,4 +1013,4 @@ int main(int argc, char** argv) { } } return 0; -} \ No newline at end of file +} diff --git a/tests/fibonacci.ppl b/tests/fibonacci.ppl new file mode 100644 index 0000000..b26d9df --- /dev/null +++ b/tests/fibonacci.ppl @@ -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)) diff --git a/tests/functions.ppl b/tests/functions.ppl new file mode 100644 index 0000000..a889ed4 --- /dev/null +++ b/tests/functions.ppl @@ -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)