From 536e55e75abe38f8e11b17c574a9efa16b3e2239 Mon Sep 17 00:00:00 2001 From: Riyyi Date: Tue, 14 Nov 2023 23:34:20 +0100 Subject: [PATCH] Everywhere: Add docstring support --- lisp/load.bl | 10 +- lisp/other.bl | 2 +- lisp/predicate.bl | 5 +- src/ast.cpp | 4 +- src/ast.h | 10 +- src/env/environment.cpp | 15 +- src/env/environment.h | 12 +- src/env/functions/collection-access.cpp | 14 ++ src/env/functions/collection-constructor.cpp | 10 + src/env/functions/collection-modify.cpp | 18 ++ src/env/functions/compare.cpp | 10 +- src/env/functions/convert.cpp | 14 +- src/env/functions/format.cpp | 8 +- src/env/functions/meta.cpp | 4 + src/env/functions/mutable.cpp | 8 + src/env/functions/operators.cpp | 13 ++ src/env/functions/other.cpp | 6 +- src/env/functions/predicate.cpp | 32 +-- src/env/functions/repl.cpp | 8 + src/env/macro.h | 10 +- src/eval-special-form.cpp | 203 +++++++++++++++++-- src/eval.cpp | 10 + src/eval.h | 15 +- src/macro.h | 22 ++ 24 files changed, 401 insertions(+), 62 deletions(-) create mode 100644 src/macro.h diff --git a/lisp/load.bl b/lisp/load.bl index 5fa377f..6a59644 100644 --- a/lisp/load.bl +++ b/lisp/load.bl @@ -1,9 +1,11 @@ -(defn load-file [filename] - (eval (read-string (str "(do " (slurp filename) "\nnil)")))) +(defn load-file [file] + "Load the Lisp file named FILE." + (eval (read-string (str "(do " (slurp file) "\nnil)")))) -(defn load [filename] - (eval (read-string (str "(let* [] (do " (slurp filename) "))")))) +(defn load [file] + "Load the Lisp file named FILE." + (eval (read-string (str "(let* [] (do " (slurp file) "))")))) ;; Local Variables: ;; eval: (emacs-lisp-mode) diff --git a/lisp/other.bl b/lisp/other.bl index 2dbf38d..722746e 100644 --- a/lisp/other.bl +++ b/lisp/other.bl @@ -1,5 +1,5 @@ -(def! *host-language* "C++") +(def *host-language* "C++") ;; Local Variables: ;; eval: (emacs-lisp-mode) diff --git a/lisp/predicate.bl b/lisp/predicate.bl index 93df0b1..b69d304 100644 --- a/lisp/predicate.bl +++ b/lisp/predicate.bl @@ -1,6 +1,7 @@ -(defn not [cond] - (if cond false true)) +(defn not [object] + "Return true if OBJECT is nil or false, and return false otherwise." + (if object false true)) ;; Local Variables: ;; eval: (emacs-lisp-mode) diff --git a/src/ast.cpp b/src/ast.cpp index 7936420..6fd9df4 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -225,8 +225,10 @@ Callable::Callable(ValuePtr meta) // ----------------------------------------- -Function::Function(const std::string& name, FunctionType function) +Function::Function(std::string_view name, std::string_view signature, std::string_view documentation, FunctionType function) : m_name(name) + , m_signature(signature) + , m_documentation(documentation) , m_function(function) { } diff --git a/src/ast.h b/src/ast.h index 77996a6..e39f087 100644 --- a/src/ast.h +++ b/src/ast.h @@ -332,11 +332,13 @@ using FunctionType = std::function Environment::s_functions; +std::vector Environment::s_function_parts; std::vector Environment::s_lambdas; EnvironmentPtr Environment::create() @@ -118,15 +118,20 @@ void Environment::loadFunctions() } } -void Environment::registerFunction(const std::string& name, FunctionType function) +void Environment::registerFunction(FunctionParts function_parts) { - s_functions.insert_or_assign(name, function); + s_function_parts.push_back(function_parts); } void Environment::installFunctions(EnvironmentPtr env) { - for (const auto& [name, function] : s_functions) { - env->set(name, makePtr(name, function)); + for (const auto& function_parts : s_function_parts) { + env->set(std::string(function_parts.name), + makePtr( + function_parts.name, + function_parts.signature, + function_parts.documentation, + function_parts.function)); } for (const auto& lambda : s_lambdas) { // Ensure all s-exprs are run with (do) diff --git a/src/env/environment.h b/src/env/environment.h index 2b56445..ac7c93b 100644 --- a/src/env/environment.h +++ b/src/env/environment.h @@ -16,6 +16,14 @@ namespace blaze { +// All of these combined become a Function in the Environment +struct FunctionParts { + std::string_view name; + std::string_view signature; + std::string_view documentation; + FunctionType function; +}; + class Environment { public: virtual ~Environment() = default; @@ -26,7 +34,7 @@ public: static EnvironmentPtr create(const ValuePtr lambda, ValueVector&& arguments); static void loadFunctions(); - static void registerFunction(const std::string& name, FunctionType function); + static void registerFunction(FunctionParts function_parts); static void installFunctions(EnvironmentPtr env); bool exists(const std::string& symbol); @@ -53,7 +61,7 @@ private: EnvironmentPtr m_outer { nullptr }; std::unordered_map m_values; - static std::unordered_map s_functions; + static std::vector s_function_parts; static std::vector s_lambdas; }; diff --git a/src/env/functions/collection-access.cpp b/src/env/functions/collection-access.cpp index 8063f24..a2c97bb 100644 --- a/src/env/functions/collection-access.cpp +++ b/src/env/functions/collection-access.cpp @@ -21,6 +21,8 @@ void Environment::loadCollectionAccess() // (count {:foo 2 :bar 3}) -> 2 ADD_FUNCTION( "count", + "", + "", { CHECK_ARG_COUNT_IS("count", SIZE(), 1); @@ -48,6 +50,8 @@ void Environment::loadCollectionAccess() // (first (list 1 2 3)) -> 1 ADD_FUNCTION( "first", + "", + "", { CHECK_ARG_COUNT_IS("first", SIZE(), 1); @@ -64,6 +68,8 @@ void Environment::loadCollectionAccess() // (nth (list 1 2 3) 0) -> 1 ADD_FUNCTION( "nth", + "", + "", { CHECK_ARG_COUNT_IS("nth", SIZE(), 2); @@ -83,6 +89,8 @@ void Environment::loadCollectionAccess() // (rest (list 1 2 3)) -> (2 3) ADD_FUNCTION( "rest", + "", + "", { CHECK_ARG_COUNT_IS("rest", SIZE(), 1); @@ -101,6 +109,8 @@ void Environment::loadCollectionAccess() // (get {:kw "value"} :kw) -> "value" ADD_FUNCTION( "get", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("get", SIZE(), 1); @@ -123,6 +133,8 @@ void Environment::loadCollectionAccess() // (keys {"foo" 3 :bar 5}) -> ("foo" :bar) ADD_FUNCTION( "keys", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("keys", SIZE(), 1); @@ -149,6 +161,8 @@ void Environment::loadCollectionAccess() // (vals {"foo" 3 :bar 5}) -> (3 5) ADD_FUNCTION( "vals", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("vals", SIZE(), 1); diff --git a/src/env/functions/collection-constructor.cpp b/src/env/functions/collection-constructor.cpp index fcf99b3..b729c08 100644 --- a/src/env/functions/collection-constructor.cpp +++ b/src/env/functions/collection-constructor.cpp @@ -19,6 +19,8 @@ void Environment::loadCollectionConstructor() // (list 1 2) -> (1 2) ADD_FUNCTION( "list", + "", + "", { return makePtr(begin, end); }); @@ -26,6 +28,8 @@ void Environment::loadCollectionConstructor() // (make-list 4 nil) -> (nil nil nil nil) ADD_FUNCTION( "make-list", + "", + "", { CHECK_ARG_COUNT_IS("make-list", SIZE(), 2); @@ -80,6 +84,8 @@ void Environment::loadCollectionConstructor() // (vec (list 1 2 3)) ADD_FUNCTION( "vec", + "", + "", { CHECK_ARG_COUNT_IS("vec", SIZE(), 1); @@ -95,6 +101,8 @@ void Environment::loadCollectionConstructor() // (vector 1 2 3) -> [1 2 3] ADD_FUNCTION( "vector", + "", + "", { auto result = makePtr(); @@ -106,6 +114,8 @@ void Environment::loadCollectionConstructor() // (hash-map "foo" 5 :bar 10) -> {"foo" 5 :bar 10} ADD_FUNCTION( "hash-map", + "", + "", { CHECK_ARG_COUNT_EVEN("hash-map", SIZE()); diff --git a/src/env/functions/collection-modify.cpp b/src/env/functions/collection-modify.cpp index 1d4bf84..c68252d 100644 --- a/src/env/functions/collection-modify.cpp +++ b/src/env/functions/collection-modify.cpp @@ -21,6 +21,8 @@ void Environment::loadCollectionModify() // (apply + 1 2 (list 3 4)) -> (+ 1 2 3 4) -> 10 ADD_FUNCTION( "apply", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("apply", SIZE(), 2); @@ -54,6 +56,8 @@ void Environment::loadCollectionModify() // (cons 1 (list 2 3)) ADD_FUNCTION( "cons", + "", + "", { CHECK_ARG_COUNT_IS("cons", SIZE(), 2); @@ -73,6 +77,8 @@ void Environment::loadCollectionModify() // (concat (list 1) (list 2 3)) -> (1 2 3) ADD_FUNCTION( "concat", + "", + "", { size_t count = 0; for (auto it = begin; it != end; ++it) { @@ -95,6 +101,8 @@ void Environment::loadCollectionModify() // (conj [1 2 3] 4 5 6) -> [1 2 3 4 5 6] ADD_FUNCTION( "conj", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("conj", SIZE(), 1); @@ -123,6 +131,8 @@ void Environment::loadCollectionModify() // (map (fn* (x) (* x 2)) (list 1 2 3)) -> (2 4 6) ADD_FUNCTION( "map", + "", + "", { CHECK_ARG_COUNT_IS("map", SIZE(), 2); @@ -152,6 +162,8 @@ void Environment::loadCollectionModify() // (set-nth (list 1 2 3) 1 "foo") -> (1 "foo" 3) ADD_FUNCTION( "set-nth", + "", + "", { CHECK_ARG_COUNT_IS("set-nth-element", SIZE(), 3); @@ -180,6 +192,8 @@ void Environment::loadCollectionModify() // (seq "foo") -> ("f" "o" "o") ADD_FUNCTION( "seq", + "", + "", { CHECK_ARG_COUNT_IS("seq", SIZE(), 1); @@ -230,6 +244,8 @@ void Environment::loadCollectionModify() // (assoc {:a 1 :b 2} :a 3 :c 1) -> {:a 3 :b 2 :c 1} ADD_FUNCTION( "assoc", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("assoc", SIZE(), 1); @@ -250,6 +266,8 @@ void Environment::loadCollectionModify() // (dissoc {:a 1 :b 2 :c 3} :a :c :d) -> {:b 2} ADD_FUNCTION( "dissoc", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("dissoc", SIZE(), 1); diff --git a/src/env/functions/compare.cpp b/src/env/functions/compare.cpp index 767cd15..de3e0b5 100644 --- a/src/env/functions/compare.cpp +++ b/src/env/functions/compare.cpp @@ -40,10 +40,10 @@ void Environment::loadCompare() return makePtr((result) ? Constant::True : Constant::False); \ } - ADD_FUNCTION("<", NUMBER_COMPARE(<)); - ADD_FUNCTION("<=", NUMBER_COMPARE(<=)); - ADD_FUNCTION(">", NUMBER_COMPARE(>)); - ADD_FUNCTION(">=", NUMBER_COMPARE(>=)); + ADD_FUNCTION("<", "", "", NUMBER_COMPARE(<)); + ADD_FUNCTION("<=", "", "", NUMBER_COMPARE(<=)); + ADD_FUNCTION(">", "", "", NUMBER_COMPARE(>)); + ADD_FUNCTION(">=", "", "", NUMBER_COMPARE(>=)); // ----------------------------------------- @@ -51,6 +51,8 @@ void Environment::loadCompare() // (= "foo" "foo") -> true ADD_FUNCTION( "=", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("=", SIZE(), 2); diff --git a/src/env/functions/convert.cpp b/src/env/functions/convert.cpp index 9fc5aad..76f07f6 100644 --- a/src/env/functions/convert.cpp +++ b/src/env/functions/convert.cpp @@ -18,6 +18,8 @@ void Environment::loadConvert() // (number-to-string 123) -> "123" ADD_FUNCTION( "number-to-string", + "", + "", { CHECK_ARG_COUNT_IS("number-to-string", SIZE(), 1); @@ -37,6 +39,8 @@ void Environment::loadConvert() // (string-to-char "123") -> 49 ADD_FUNCTION( "string-to-char", + "", + "", { CHECK_ARG_COUNT_IS("string-to-char", SIZE(), 1); @@ -49,6 +53,8 @@ void Environment::loadConvert() // (string-to-number "123") -> 123 ADD_FUNCTION( "string-to-number", + "", + "", { CHECK_ARG_COUNT_IS("string-to-number", SIZE(), 1); @@ -83,14 +89,16 @@ void Environment::loadConvert() // (string-to-list "foo") -> (102 111 111) // (string-to-vector "foo") -> [102 111 111] - ADD_FUNCTION("string-to-list", STRING_TO_COLLECTION("string-to-list", List)); - ADD_FUNCTION("string-to-vector", STRING_TO_COLLECTION("string-to-vector", Vector)); + ADD_FUNCTION("string-to-list", "", "", STRING_TO_COLLECTION("string-to-list", List)); + ADD_FUNCTION("string-to-vector", "", "", STRING_TO_COLLECTION("string-to-vector", Vector)); // ------------------------------------- // (symbol "foo") -> foo ADD_FUNCTION( "symbol", + "", + "", { CHECK_ARG_COUNT_IS("symbol", SIZE(), 1); @@ -107,6 +115,8 @@ void Environment::loadConvert() // (keyword 123) -> :123 ADD_FUNCTION( "keyword", + "", + "", { CHECK_ARG_COUNT_IS("keyword", SIZE(), 1); diff --git a/src/env/functions/format.cpp b/src/env/functions/format.cpp index f12f528..d854ddd 100644 --- a/src/env/functions/format.cpp +++ b/src/env/functions/format.cpp @@ -34,8 +34,8 @@ void Environment::loadFormat() return makePtr(result); \ } - ADD_FUNCTION("str", PRINTER_STRING(false, "")); - ADD_FUNCTION("pr-str", PRINTER_STRING(true, " ")); + ADD_FUNCTION("str", "", "", PRINTER_STRING(false, "")); + ADD_FUNCTION("pr-str", "", "", PRINTER_STRING(true, " ")); #define PRINTER_PRINT(print_readably) \ { \ @@ -52,8 +52,8 @@ void Environment::loadFormat() return makePtr(); \ } - ADD_FUNCTION("prn", PRINTER_PRINT(true)); - ADD_FUNCTION("println", PRINTER_PRINT(false)); + ADD_FUNCTION("prn", "", "", PRINTER_PRINT(true)); + ADD_FUNCTION("println", "", "", PRINTER_PRINT(false)); } } // namespace blaze diff --git a/src/env/functions/meta.cpp b/src/env/functions/meta.cpp index a520b0b..b9bb686 100644 --- a/src/env/functions/meta.cpp +++ b/src/env/functions/meta.cpp @@ -17,6 +17,8 @@ void Environment::loadMeta() // (meta [1 2 3]) ADD_FUNCTION( "meta", + "", + "", { CHECK_ARG_COUNT_IS("meta", SIZE(), 1); @@ -36,6 +38,8 @@ void Environment::loadMeta() // (with-meta [1 2 3] "some metadata") ADD_FUNCTION( "with-meta", + "", + "", { CHECK_ARG_COUNT_IS("with-meta", SIZE(), 2); diff --git a/src/env/functions/mutable.cpp b/src/env/functions/mutable.cpp index 6e17179..4d691dc 100644 --- a/src/env/functions/mutable.cpp +++ b/src/env/functions/mutable.cpp @@ -20,6 +20,8 @@ void Environment::loadMutable() // (atom 1) ADD_FUNCTION( "atom", + "", + "", { CHECK_ARG_COUNT_IS("atom", SIZE(), 1); @@ -29,6 +31,8 @@ void Environment::loadMutable() // (deref myatom) ADD_FUNCTION( "deref", + "", + "", { CHECK_ARG_COUNT_IS("deref", SIZE(), 1); @@ -40,6 +44,8 @@ void Environment::loadMutable() // (reset! myatom 2) ADD_FUNCTION( "reset!", + "", + "", { CHECK_ARG_COUNT_IS("reset!", SIZE(), 2); @@ -54,6 +60,8 @@ void Environment::loadMutable() // (swap! myatom (fn* [x y] (+ 1 x y)) 2) -> (deref (def! myatom (atom ((fn* [x y] (+ 1 x y)) (deref myatom) 2)))) ADD_FUNCTION( "swap!", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("swap!", SIZE(), 2); diff --git a/src/env/functions/operators.cpp b/src/env/functions/operators.cpp index 98922a9..7cb3164 100644 --- a/src/env/functions/operators.cpp +++ b/src/env/functions/operators.cpp @@ -16,6 +16,8 @@ void Environment::loadOperators() { ADD_FUNCTION( "+", + "number...", + "Return the sum of any amount of arguments, where NUMBER is of type number.", { int64_t result = 0; @@ -29,6 +31,11 @@ void Environment::loadOperators() ADD_FUNCTION( "-", + "[number] subtract...", + R"(Negate NUMBER or SUBTRACT numbers and return the result. + +With one arg, negates it. With more than one arg, +subtracts all but the first from the first.)", { size_t length = SIZE(); if (length == 0) { @@ -54,6 +61,8 @@ void Environment::loadOperators() ADD_FUNCTION( "*", + "", + "", { int64_t result = 1; @@ -67,6 +76,8 @@ void Environment::loadOperators() ADD_FUNCTION( "/", + "", + "", { CHECK_ARG_COUNT_AT_LEAST("/", SIZE(), 1); @@ -86,6 +97,8 @@ void Environment::loadOperators() // (% 5 2) -> 1 ADD_FUNCTION( "%", + "", + "", { CHECK_ARG_COUNT_IS("/", SIZE(), 2); diff --git a/src/env/functions/other.cpp b/src/env/functions/other.cpp index 57f8d1f..9db73ad 100644 --- a/src/env/functions/other.cpp +++ b/src/env/functions/other.cpp @@ -5,9 +5,7 @@ */ #include // std::chrono::sytem_clock -#include // size_t #include // int64_t -#include // std::static_pointer_cast #include "ast.h" #include "env/macro.h" @@ -22,6 +20,8 @@ void Environment::loadOther() // (throw x) ADD_FUNCTION( "throw", + "", + "", { CHECK_ARG_COUNT_IS("throw", SIZE(), 1); @@ -35,6 +35,8 @@ void Environment::loadOther() // (time-ms) ADD_FUNCTION( "time-ms", + "", + "", { CHECK_ARG_COUNT_IS("time-ms", SIZE(), 0); diff --git a/src/env/functions/predicate.cpp b/src/env/functions/predicate.cpp index ae7d474..522be78 100644 --- a/src/env/functions/predicate.cpp +++ b/src/env/functions/predicate.cpp @@ -22,9 +22,9 @@ void Environment::loadPredicate() } // (nil? nil) - ADD_FUNCTION("nil?", IS_CONSTANT("nil?", Constant::Nil)); - ADD_FUNCTION("true?", IS_CONSTANT("true?", Constant::True)); - ADD_FUNCTION("false?", IS_CONSTANT("false?", Constant::False)); + ADD_FUNCTION("nil?", "", "", IS_CONSTANT("nil?", Constant::Nil)); + ADD_FUNCTION("true?", "", "", IS_CONSTANT("true?", Constant::True)); + ADD_FUNCTION("false?", "", "", IS_CONSTANT("false?", Constant::False)); // ----------------------------------------- @@ -47,18 +47,20 @@ void Environment::loadPredicate() } // (symbol? 'foo) - ADD_FUNCTION("atom?", IS_TYPE(Atom)); - ADD_FUNCTION("keyword?", IS_TYPE(Keyword)); - ADD_FUNCTION("list?", IS_TYPE(List)); - ADD_FUNCTION("map?", IS_TYPE(HashMap)); - ADD_FUNCTION("number?", IS_TYPE(Number)); - ADD_FUNCTION("sequential?", IS_TYPE(Collection)); - ADD_FUNCTION("string?", IS_TYPE(String)); - ADD_FUNCTION("symbol?", IS_TYPE(Symbol)); - ADD_FUNCTION("vector?", IS_TYPE(Vector)); + ADD_FUNCTION("atom?", "", "", IS_TYPE(Atom)); + ADD_FUNCTION("keyword?", "", "", IS_TYPE(Keyword)); + ADD_FUNCTION("list?", "", "", IS_TYPE(List)); + ADD_FUNCTION("map?", "", "", IS_TYPE(HashMap)); + ADD_FUNCTION("number?", "", "", IS_TYPE(Number)); + ADD_FUNCTION("sequential?", "", "", IS_TYPE(Collection)); + ADD_FUNCTION("string?", "", "", IS_TYPE(String)); + ADD_FUNCTION("symbol?", "", "", IS_TYPE(Symbol)); + ADD_FUNCTION("vector?", "", "", IS_TYPE(Vector)); ADD_FUNCTION( "fn?", + "", + "", { bool result = true; @@ -78,6 +80,8 @@ void Environment::loadPredicate() ADD_FUNCTION( "macro?", + "", + "", { bool result = true; @@ -101,6 +105,8 @@ void Environment::loadPredicate() // (contains? {"bar" 5} "foo") -> false ADD_FUNCTION( "contains?", + "", + "", { CHECK_ARG_COUNT_IS("contains?", SIZE(), 2); @@ -117,6 +123,8 @@ void Environment::loadPredicate() // (empty? [] [1 2 3] []) -> false ADD_FUNCTION( "empty?", + "", + "", { bool result = true; diff --git a/src/env/functions/repl.cpp b/src/env/functions/repl.cpp index 9bb35d2..92640a9 100644 --- a/src/env/functions/repl.cpp +++ b/src/env/functions/repl.cpp @@ -19,6 +19,8 @@ void Environment::loadRepl() // REPL reader ADD_FUNCTION( "read-string", + "", + "", { CHECK_ARG_COUNT_IS("read-string", SIZE(), 1); @@ -31,6 +33,8 @@ void Environment::loadRepl() // Read file contents ADD_FUNCTION( "slurp", + "", + "", { CHECK_ARG_COUNT_IS("slurp", SIZE(), 1); @@ -45,6 +49,8 @@ void Environment::loadRepl() // Prompt readline ADD_FUNCTION( "readline", + "", + "", { CHECK_ARG_COUNT_IS("readline", SIZE(), 1); @@ -56,6 +62,8 @@ void Environment::loadRepl() // REPL eval ADD_FUNCTION( "eval", + "", + "", { CHECK_ARG_COUNT_IS("eval", SIZE(), 1); diff --git a/src/env/macro.h b/src/env/macro.h index dba2625..e3af93d 100644 --- a/src/env/macro.h +++ b/src/env/macro.h @@ -8,9 +8,11 @@ #include "env/environment.h" -#define ADD_FUNCTION(symbol, lambda) \ - Environment::registerFunction( \ - symbol, \ - [](ValueVectorConstIt begin, ValueVectorConstIt end) -> blaze::ValuePtr lambda); +#define ADD_FUNCTION(name, signature, documentation, lambda) \ + Environment::registerFunction( \ + { name, \ + signature, \ + documentation, \ + [](ValueVectorConstIt begin, ValueVectorConstIt end) -> blaze::ValuePtr lambda }); #define SIZE() std::distance(begin, end) diff --git a/src/eval-special-form.cpp b/src/eval-special-form.cpp index edc75a0..766fbc0 100644 --- a/src/eval-special-form.cpp +++ b/src/eval-special-form.cpp @@ -4,17 +4,26 @@ * SPDX-License-Identifier: MIT */ -#include // std::distance, std::next, std::prev +#include // std::find_if, std::transform +#include // std::toupper +#include // std::distance, std::next, std::prev #include #include #include #include +#include "ruc/format/color.h" +#include "ruc/format/format.h" +#include "ruc/format/print.h" + #include "ast.h" #include "env/environment.h" #include "error.h" #include "eval.h" #include "forward.h" +#include "macro.h" +#include "printer.h" +#include "settings.h" #include "types.h" #include "util.h" @@ -22,7 +31,7 @@ namespace blaze { static ValuePtr evalQuasiQuoteImpl(ValuePtr ast); -// (def! x 2) +EVAL_FUNCTION("def!", "symbol value", "Set SYMBOL to the value VALUE."); ValuePtr Eval::evalDef(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("def!", nodes.size(), 2); @@ -44,7 +53,13 @@ ValuePtr Eval::evalDef(const ValueVector& nodes, EnvironmentPtr env) return env->set(symbol->symbol(), value); } -// (defmacro! x (fn* (x) x)) +EVAL_FUNCTION("defmacro!", "symbol function", + R"(Define SYMBOL as a macro. + +When the macro is called, as in (NAME ARGS...), +the FUNCTION (fn* ARGLIST BODY...) is applied to +the list ARGS... as it appears in the expression, +and the result should be a form to be evaluated instead of the original.)"); ValuePtr Eval::evalDefMacro(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("defmacro!", nodes.size(), 2); @@ -67,7 +82,139 @@ ValuePtr Eval::evalDefMacro(const ValueVector& nodes, EnvironmentPtr env) return env->set(symbol->symbol(), makePtr(*lambda)); } -// (fn* (x) x) +EVAL_FUNCTION("describe", "symbol", "Display the full documentation of SYMBOL."); +ValuePtr Eval::evalDescribe(const ValueVector& nodes, EnvironmentPtr env) +{ + CHECK_ARG_COUNT_IS("describe", nodes.size(), 1); + + // First argument needs to be a Symbol + VALUE_CAST(symbol, Symbol, nodes.front()); + auto symbol_string = symbol->symbol(); + + std::string type; + std::string signature; + std::string documentation; + std::string value_string; + + bool pretty_print = Settings::the().get("pretty-print") == "1"; + auto bold = fg(ruc::format::TerminalColor::None) | ruc::format::Emphasis::Bold; + + auto describe = [&]() { + print("{} is a {}.\n\n", symbol_string, type); + + if (!signature.empty()) { + pretty_print ? print(bold, "Signature\n") : print("Signature\n"); + print("({})\n", signature); + } + + if (!documentation.empty()) { + pretty_print ? print(bold, "\nDocumentation\n") : print("\nDocumentation\n"); + print("{}\n", documentation); + } + + if (!value_string.empty()) { + pretty_print ? print(bold, "Value\n") : print("Value\n"); + print("{}\n", value_string); + } + }; + + // Verify if symbol is a special form + auto special_form = std::find_if( + s_special_form_parts.begin(), + s_special_form_parts.end(), + [&symbol_string](const SpecialFormParts& special_form_parts) { + return special_form_parts.name == symbol_string; + }); + + // If symbol is not special form, lookup in the environment + ValuePtr value; + if (special_form == s_special_form_parts.end()) { + value = env->get(symbol_string); + if (!value) { + Error::the().add(format("'{}' not found", symbol_string)); + return nullptr; + } + } + + // Variable + if (special_form == s_special_form_parts.end() && !is(value.get())) { + type = "variable"; + + Printer printer; + value_string = format("{}", printer.printNoErrorCheck(value, true)); + + describe(); + + return nullptr; + } + + signature = pretty_print ? format(fg(ruc::format::TerminalColor::BrightBlue), "{}", symbol_string) + : symbol_string; + + // Special form + if (special_form != s_special_form_parts.end()) { + type = "special form"; + + std::string signature_lower = std::string(special_form->signature); + std::transform(signature_lower.begin(), signature_lower.end(), signature_lower.begin(), ::toupper); + signature += !signature_lower.empty() ? " " : ""; + signature += signature_lower; + + documentation = special_form->documentation; + + describe(); + + return nullptr; + } + + // Function / lambda / macro + if (is(value.get())) { + type = "function"; + + auto function = std::static_pointer_cast(value); + signature += !function->signature().empty() ? " " : ""; + signature += function->signature(); + + documentation = function->documentation(); + } + else if (is(value.get()) || is(value.get())) { + type = is(value.get()) ? "function" : "macro"; + + auto lambda = std::static_pointer_cast(value); + auto bindings = lambda->bindings(); + std::string binding; + for (size_t i = 0; i < bindings.size(); ++i) { + binding = bindings[i]; + std::transform(binding.begin(), binding.end(), binding.begin(), ::toupper); + signature += " " + binding; + } + + auto body = lambda->body(); + if (is(body.get())) { + documentation = std::static_pointer_cast(body)->data(); + } + else if (is(body.get())) { + VALUE_CAST(list, List, body); + if (list->size() > 1) { + auto second = list->nodesRead()[1]; + if (is(second.get())) { + documentation = std::static_pointer_cast(second)->data(); + } + } + } + } + + describe(); + + return nullptr; +} + +EVAL_FUNCTION("fn*", "args [docstring] body...", R"(Return an anonymous function. + +ARGS should take the form of an argument list or vector. +DOCSTRING is an optional documentation string. + If present, it should describe how to call the function. +BODY should be a list of Lisp expressions.)"); ValuePtr Eval::evalFn(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_AT_LEAST("fn*", nodes.size(), 2); @@ -102,6 +249,7 @@ ValuePtr Eval::evalFn(const ValueVector& nodes, EnvironmentPtr env) // ----------------------------------------- // (quasiquoteexpand x) +EVAL_FUNCTION("quasiquoteexpand", "arg", ""); // TODO ValuePtr Eval::evalQuasiQuoteExpand(const ValueVector& nodes) { CHECK_ARG_COUNT_IS("quasiquoteexpand", nodes.size(), 1); @@ -109,7 +257,7 @@ ValuePtr Eval::evalQuasiQuoteExpand(const ValueVector& nodes) return evalQuasiQuoteImpl(nodes.front()); } -// (quote x) +EVAL_FUNCTION("quote", "arg", "Return the ARG, without evaluating it. (quote x) yields x."); ValuePtr Eval::evalQuote(const ValueVector& nodes) { CHECK_ARG_COUNT_IS("quote", nodes.size(), 1); @@ -117,7 +265,13 @@ ValuePtr Eval::evalQuote(const ValueVector& nodes) return nodes.front(); } -// (try* x ... (catch* y z)) +EVAL_FUNCTION("try*", "body... [catch]", R"(Eval BODY allowing exceptions to get caught. + +CATCH should take the form of (catch* binding handler). + +The BODY is evaluated, if it throws an exception, then form CATCH is +handled by creating a new environment that binds the symbol BINDING +to the value of the exception that was thrown. Finally, HANDLER is evaluated.)"); ValuePtr Eval::evalTry(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_AT_LEAST("try*", nodes.size(), 1); @@ -169,11 +323,11 @@ ValuePtr Eval::evalTry(const ValueVector& nodes, EnvironmentPtr env) VALUE_CAST(catch_binding, Symbol, (*std::next(catch_nodes.begin()))); - // Create new Environment that binds 'y' to the value of the exception + // Create new Environment that binds 'binding' to the value of the exception auto catch_env = Environment::create(env); catch_env->set(catch_binding->symbol(), error); - // Evaluate 'z' using the new Environment + // Evaluate 'handler' using the new Environment m_ast = catch_nodes.back(); m_env = catch_env; return evalImpl(); @@ -181,7 +335,10 @@ ValuePtr Eval::evalTry(const ValueVector& nodes, EnvironmentPtr env) // ----------------------------------------- -// (and 1 2 3) +EVAL_FUNCTION("and", "args...", R"(Eval ARGS until one of them yields nil, then return nil. + +The remaining args are not evalled at all. +If no arg yields nil, return the last arg's value.)"); void Eval::evalAnd(const ValueVector& nodes, EnvironmentPtr env) { ValuePtr result = makePtr(Constant::True); @@ -205,7 +362,7 @@ void Eval::evalAnd(const ValueVector& nodes, EnvironmentPtr env) return; // TCO } -// (do 1 2 3) +EVAL_FUNCTION("do", "body...", "Eval BODY forms sequentially and return value of the last one."); void Eval::evalDo(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_AT_LEAST("do", nodes.size(), 1, void()); @@ -223,7 +380,11 @@ void Eval::evalDo(const ValueVector& nodes, EnvironmentPtr env) return; // TCO } -// (if x true false) +EVAL_FUNCTION("if", "COND THEN [ELSE]", R"(If COND yields non-nil, do THEN, else do ELSE. + +Returns the value of THEN or the value of ELSE. +Both THEN and ELSE must be one expression. +If COND yields nil, and there is no ELSE, the value is nil.)"); void Eval::evalIf(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_BETWEEN("if", nodes.size(), 2, 3, void()); @@ -247,7 +408,12 @@ void Eval::evalIf(const ValueVector& nodes, EnvironmentPtr env) return; // TCO } -// (let* (x 1) x) +EVAL_FUNCTION("let*", "varlist body", R"(Bind variables accoring to VARLIST then eval BODY. + +The value of the BODY form is returned. +VARLIST is a list or vector with an even amount of elements, +where each odd number is a symbol gets bind the even element. +All even elements are evalled before any symbols are bound.)"); void Eval::evalLet(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("let*", nodes.size(), 2, void()); @@ -307,6 +473,7 @@ static bool isMacroCall(ValuePtr ast, EnvironmentPtr env) return true; } +EVAL_FUNCTION("macroexpand-1", "expression", "Macroexpand EXPRESSION and pretty-print its value."); void Eval::evalMacroExpand1(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("macroexpand-1", nodes.size(), 1, void()); @@ -329,7 +496,10 @@ void Eval::evalMacroExpand1(const ValueVector& nodes, EnvironmentPtr env) // ----------------------------------------- -// (or 1 2 3) +EVAL_FUNCTION("or", "args...", R"(Eval ARGS until one of them yields non-nil, then return that value. + +The remaining args are not evalled at all. +If all args return nil, return nil.)"); void Eval::evalOr(const ValueVector& nodes, EnvironmentPtr env) { ValuePtr result; @@ -442,6 +612,7 @@ static ValuePtr evalQuasiQuoteImpl(ValuePtr ast) } // (quasiquote x) +EVAL_FUNCTION("quasiquote", "arg", R"()"); // TODO void Eval::evalQuasiQuote(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("quasiquote", nodes.size(), 1, void()); @@ -456,6 +627,12 @@ void Eval::evalQuasiQuote(const ValueVector& nodes, EnvironmentPtr env) // ----------------------------------------- // (while true body...) +EVAL_FUNCTION("while", "test body...", R"(If TEST yields non-nil, eval BODY... and repeat + +The order of execution is thus TEST, BODY, TEST, BODY and so on +until TEST returns nil. + +The value of a while form is always nil.)"); void Eval::evalWhile(const ValueVector& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_AT_LEAST("while", nodes.size(), 2, void()); diff --git a/src/eval.cpp b/src/eval.cpp index 40f99aa..2b67690 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -20,6 +20,8 @@ namespace blaze { +std::vector Eval::s_special_form_parts; + Eval::Eval(ValuePtr ast, EnvironmentPtr env) : m_ast(ast) , m_env(env) @@ -29,6 +31,11 @@ Eval::Eval(ValuePtr ast, EnvironmentPtr env) // ----------------------------------------- +void Eval::registerSpecialForm(SpecialFormParts special_form_parts) +{ + s_special_form_parts.push_back(special_form_parts); +} + void Eval::eval() { m_ast = evalImpl(); @@ -80,6 +87,9 @@ ValuePtr Eval::evalImpl() if (symbol == "defmacro!") { return evalDefMacro(nodes, env); } + if (symbol == "describe") { + return evalDescribe(nodes, env); + } if (symbol == "fn*") { return evalFn(nodes, env); } diff --git a/src/eval.h b/src/eval.h index 7e3c9d9..5a1c79c 100644 --- a/src/eval.h +++ b/src/eval.h @@ -6,21 +6,27 @@ #pragma once -#include -#include +#include #include "env/environment.h" #include "forward.h" namespace blaze { -class List; +// All of these combined become a Function in the Environment +struct SpecialFormParts { + std::string_view name; + std::string_view signature; + std::string_view documentation; +}; class Eval { public: Eval(ValuePtr ast, EnvironmentPtr env); virtual ~Eval() = default; + static void registerSpecialForm(SpecialFormParts special_form_parts); + void eval(); ValuePtr ast() const { return m_ast; } @@ -33,6 +39,7 @@ private: ValuePtr evalDef(const ValueVector& nodes, EnvironmentPtr env); ValuePtr evalDefMacro(const ValueVector& nodes, EnvironmentPtr env); + ValuePtr evalDescribe(const ValueVector& nodes, EnvironmentPtr env); ValuePtr evalFn(const ValueVector& nodes, EnvironmentPtr env); ValuePtr evalQuasiQuoteExpand(const ValueVector& nodes); ValuePtr evalQuote(const ValueVector& nodes); @@ -52,6 +59,8 @@ private: ValuePtr m_ast; EnvironmentPtr m_env; EnvironmentPtr m_outer_env; + + static std::vector s_special_form_parts; }; } // namespace blaze diff --git a/src/macro.h b/src/macro.h new file mode 100644 index 0000000..98ada68 --- /dev/null +++ b/src/macro.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Riyi + * + * SPDX-License-Identifier: MIT + */ + +#include "eval.h" + +#define CONCAT(a, b) CONCAT_IMPL(a, b) +#define CONCAT_IMPL(a, b) a##b + +#define EVAL_FUNCTION_IMPL(name, signature, documentation, struct_name) \ + struct struct_name { \ + struct_name() \ + { \ + Eval::registerSpecialForm({ name, signature, documentation }); \ + } \ + }; \ + static struct struct_name struct_name; // NOLINT(clang-diagnostic-unused-function) + +#define EVAL_FUNCTION(name, signature, documentation) \ + EVAL_FUNCTION_IMPL(name, signature, documentation, CONCAT(__EVAL_STRUCT_, __COUNTER__))