Compare commits

...

21 Commits

Author SHA1 Message Date
Riyyi 11f0553b5a AST+Env+Printer+Reader: Implement floating point numbers 10 months ago
Riyyi 705e80ad6b Everywhere: Split REPL from main(), put settings in environment 10 months ago
Riyyi 4cc2acc8a0 Printer: Rename node -> value 10 months ago
Riyyi f5dc1168eb Reader+Env: Add dump function 10 months ago
Riyyi 536e55e75a Everywhere: Add docstring support 10 months ago
Riyyi 25871cd5d5 Print: Make strings brightgreen color 10 months ago
Riyyi 929fb5d645 Env+Eval: Tweak (and) and (-) 10 months ago
Riyyi 9db041946e Env: Add string/number conversion functions 10 months ago
Riyyi b74f3448b2 Eval: Allow multiple s-expr in fn* 10 months ago
Riyyi b727f7147e Eval: Add and/or special forms 10 months ago
Riyyi c6c6d69e73 Eval: Allow multiple s-expr in try* 11 months ago
Riyyi bb6f3e7496 Meta: Change README.org title and description 11 months ago
Riyyi 1915621427 Eval: Change (macroexpand) into (macroexpand-1) 11 months ago
Riyyi 9895195410 Env: Add support to (count) for HashMap type 1 year ago
Riyyi e8206d762c Env: Load lisp code at runtime from files 1 year ago
Riyyi b65482eb68 Env: Allow load order control for native functions 1 year ago
Riyyi 67b982fd4c Eval: Merge eval, eval-ast and macroexpand 1 year ago
Riyyi d3a50abfbc Env: Organize functions better by splitting into multiple files 1 year ago
Riyyi 80b25f8c21 Eval: Add special form while 1 year ago
Riyyi 0d43512ea9 Everywhere: Do less Collection nodes copying 1 year ago
Riyyi fa4bd63dca Main: Remove step mains 1 year ago
  1. 34
      CMakeLists.txt
  2. 4
      README.org
  3. 1
      cmake/copy-lisp.cmake
  4. 12
      lisp/compare.bl
  5. 14
      lisp/init.bl
  6. 12
      lisp/load.bl
  7. 6
      lisp/other.bl
  8. 8
      lisp/predicate.bl
  9. 45
      src/ast.cpp
  10. 59
      src/ast.h
  11. 174
      src/env/environment.cpp
  12. 68
      src/env/environment.h
  13. 185
      src/env/functions/collection-access.cpp
  14. 132
      src/env/functions/collection-constructor.cpp
  15. 287
      src/env/functions/collection-modify.cpp
  16. 166
      src/env/functions/compare.cpp
  17. 149
      src/env/functions/convert.cpp
  18. 75
      src/env/functions/format.cpp
  19. 60
      src/env/functions/meta.cpp
  20. 93
      src/env/functions/mutable.cpp
  21. 166
      src/env/functions/operators.cpp
  22. 51
      src/env/functions/other.cpp
  23. 143
      src/env/functions/predicate.cpp
  24. 75
      src/env/functions/repl.cpp
  25. 18
      src/env/macro.h
  26. 102
      src/environment.cpp
  27. 37
      src/environment.h
  28. 420
      src/eval-special-form.cpp
  29. 214
      src/eval.cpp
  30. 32
      src/eval.h
  31. 12
      src/forward.h
  32. 992
      src/functions.cpp
  33. 22
      src/macro.h
  34. 84
      src/main.cpp
  35. 87
      src/printer.cpp
  36. 6
      src/printer.h
  37. 160
      src/reader.cpp
  38. 5
      src/reader.h
  39. 100
      src/repl.cpp
  40. 29
      src/repl.h
  41. 13
      src/settings.cpp
  42. 8
      src/settings.h
  43. 62
      src/step0_repl.cpp
  44. 114
      src/step1_read_print.cpp
  45. 118
      src/step2_eval.cpp
  46. 118
      src/step3_env.cpp
  47. 135
      src/step4_if_fn_do.cpp
  48. 141
      src/step5_tco.cpp
  49. 161
      src/step6_file.cpp
  50. 161
      src/step7_quote.cpp
  51. 168
      src/step8_macros.cpp
  52. 171
      src/step9_try.cpp
  53. 178
      src/stepA_mal.cpp
  54. 2
      vendor/ruc

34
CMakeLists.txt

@ -2,7 +2,7 @@
# User config between these lines
# Set project name
set(PROJECT "stepA_mal")
set(PROJECT "blaze")
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(BLAZE_STANDALONE TRUE)
@ -64,26 +64,18 @@ add_subdirectory("vendor/ruc")
# Define source files
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
file(GLOB_RECURSE EXCLUDED_SOURCES "src/step*.cpp")
list(REMOVE_ITEM PROJECT_SOURCES ${EXCLUDED_SOURCES})
function(add_step TARGET_NAME MAIN_PATH)
add_executable(${TARGET_NAME} ${PROJECT_SOURCES} ${MAIN_PATH})
target_include_directories(${TARGET_NAME} PRIVATE "src")
target_link_libraries(${TARGET_NAME} readline ruc)
endfunction()
add_executable(${PROJECT} ${PROJECT_SOURCES})
target_include_directories(${PROJECT} PRIVATE "src")
target_link_libraries(${PROJECT} readline ruc)
# ------------------------------------------
# Std target
add_step(step0_repl "src/step0_repl.cpp")
add_step(step1_read_print "src/step1_read_print.cpp")
add_step(step2_eval "src/step2_eval.cpp")
add_step(step3_env "src/step3_env.cpp")
add_step(step4_if_fn_do "src/step4_if_fn_do.cpp")
add_step(step5_tco "src/step5_tco.cpp")
add_step(step6_file "src/step6_file.cpp")
add_step(step7_quote "src/step7_quote.cpp")
add_step(step8_macros "src/step8_macros.cpp")
add_step(step9_try "src/step9_try.cpp")
add_step(stepA_mal "src/stepA_mal.cpp")
add_custom_target(${PROJECT}-lisp
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/copy-lisp.cmake
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_dependencies(${PROJECT} ${PROJECT}-lisp)
# ------------------------------------------
# Execute target
@ -97,8 +89,8 @@ add_custom_target(run
function(make_test_target target_name step_name)
add_custom_target(${target_name}
COMMAND ../vendor/mal/runtest.py --deferrable --optional ../tests/${step_name}.mal -- ./${step_name})
add_dependencies(${target_name} ${step_name})
COMMAND ../vendor/mal/runtest.py --deferrable --optional ../tests/${step_name}.mal -- ./${PROJECT})
add_dependencies(${target_name} ${PROJECT})
endfunction()
make_test_target("test0" "step0_repl")

4
README.org

@ -1,9 +1,9 @@
#+TITLE: mal - Make a Lisp
#+TITLE: blaze lisp
#+AUTHOR: Riyyi
#+LANGUAGE: en
#+OPTIONS: toc:nil
This is an implementation of the [[https://github.com/kanaka/mal][Make A Lisp]] project, done in C++20.
blaze lisp, written in C++20.
** Usage

1
cmake/copy-lisp.cmake

@ -0,0 +1 @@
file(COPY ${CMAKE_CURRENT_LIST_DIR}/../lisp DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

12
lisp/compare.bl

@ -0,0 +1,12 @@
(defmacro! cond (fn* [& xs]
(if (> (count xs) 0)
(list 'if (first xs)
(if (> (count xs) 1)
(nth xs 1)
(throw "odd number of forms to cond"))
(cons 'cond (rest (rest xs)))))))
;; Local Variables:
;; eval: (emacs-lisp-mode)
;; End:

14
lisp/init.bl

@ -0,0 +1,14 @@
(defmacro! defmacro
(fn* [name args & body]
`(defmacro! ~name (fn* ~args ~@body))))
(defmacro defn [name args & body]
`(def! ~name (fn* ~args ~@body)))
(defmacro def [name & body]
`(def! ~name ~@body))
;; Local Variables:
;; eval: (emacs-lisp-mode)
;; End:

12
lisp/load.bl

@ -0,0 +1,12 @@
(defn load-file [file]
"Load the Lisp file named FILE."
(eval (read-string (str "(do " (slurp file) "\nnil)"))))
(defn load [file]
"Load the Lisp file named FILE."
(eval (read-string (str "(let* [] (do " (slurp file) "))"))))
;; Local Variables:
;; eval: (emacs-lisp-mode)
;; End:

6
lisp/other.bl

@ -0,0 +1,6 @@
(def *host-language* "C++")
;; Local Variables:
;; eval: (emacs-lisp-mode)
;; End:

8
lisp/predicate.bl

@ -0,0 +1,8 @@
(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)
;; End:

45
src/ast.cpp

@ -7,10 +7,11 @@
#include <cstdint> // int64_t
#include <memory> // std::static_pointer_cast
#include <string>
#include <utility> // std::move
#include <vector>
#include "ast.h"
#include "environment.h"
#include "env/environment.h"
#include "error.h"
#include "forward.h"
#include "printer.h"
@ -35,6 +36,11 @@ Collection::Collection(const ValueVector& nodes)
{
}
Collection::Collection(ValueVector&& nodes) noexcept
: m_nodes(std::move(nodes))
{
}
Collection::Collection(ValueVectorIt begin, ValueVectorIt end)
: m_nodes(ValueVector(begin, end))
{
@ -64,6 +70,11 @@ List::List(const ValueVector& nodes)
{
}
List::List(ValueVector&& nodes) noexcept
: Collection(std::move(nodes))
{
}
List::List(ValueVectorIt begin, ValueVectorIt end)
: Collection(begin, end)
{
@ -86,6 +97,11 @@ Vector::Vector(const ValueVector& nodes)
{
}
Vector::Vector(ValueVector&& nodes) noexcept
: Collection(std::move(nodes))
{
}
Vector::Vector(ValueVectorIt begin, ValueVectorIt end)
: Collection(begin, end)
{
@ -169,10 +185,22 @@ Keyword::Keyword(const std::string& data)
{
}
Keyword::Keyword(int64_t number)
: m_data(std::string(1, 0x7f) + std::to_string(number)) // 127
{
}
// -----------------------------------------
Number::Number(int64_t number)
: m_number(number)
: Numeric()
, m_number(number)
{
}
Decimal::Decimal(double decimal)
: Numeric()
, m_decimal(decimal)
{
}
@ -204,8 +232,11 @@ Callable::Callable(ValuePtr meta)
// -----------------------------------------
Function::Function(const std::string& name, FunctionType function)
: m_name(name)
Function::Function(std::string_view name, std::string_view bindings, std::string_view documentation, FunctionType function)
: Callable()
, m_name(name)
, m_bindings(bindings)
, m_documentation(documentation)
, m_function(function)
{
}
@ -220,14 +251,16 @@ Function::Function(const Function& that, ValuePtr meta)
// -----------------------------------------
Lambda::Lambda(const std::vector<std::string>& bindings, ValuePtr body, EnvironmentPtr env)
: m_bindings(bindings)
: Callable()
, m_bindings(bindings)
, m_body(body)
, m_env(env)
{
}
Lambda::Lambda(const Lambda& that)
: m_bindings(that.m_bindings)
: Callable()
, m_bindings(that.m_bindings)
, m_body(that.m_body)
, m_env(that.m_env)
{

59
src/ast.h

@ -52,7 +52,9 @@ public:
virtual bool isHashMap() const { return false; }
virtual bool isString() const { return false; }
virtual bool isKeyword() const { return false; }
virtual bool isNumeric() const { return false; }
virtual bool isNumber() const { return false; }
virtual bool isDecimal() const { return false; }
virtual bool isConstant() const { return false; }
virtual bool isSymbol() const { return false; }
virtual bool isCallable() const { return false; }
@ -101,11 +103,18 @@ public:
ValuePtr front() const { return m_nodes.front(); }
ValueVector rest() const;
const ValueVector& nodes() const { return m_nodes; }
ValueVectorConstIt begin() const { return m_nodes.cbegin(); }
ValueVectorConstIt end() const { return m_nodes.cend(); }
ValueVectorConstReverseIt beginReverse() const { return m_nodes.crbegin(); }
ValueVectorConstReverseIt endReverse() const { return m_nodes.crend(); }
const ValueVector& nodesCopy() const { return m_nodes; }
std::span<const ValuePtr> nodesRead() const { return m_nodes; }
protected:
Collection() = default;
Collection(const ValueVector& nodes);
Collection(ValueVector&& nodes) noexcept;
Collection(ValueVectorIt begin, ValueVectorIt end);
Collection(ValueVectorConstIt begin, ValueVectorConstIt end);
Collection(const Collection& that, ValuePtr meta);
@ -129,6 +138,7 @@ class List final : public Collection {
public:
List() = default;
List(const ValueVector& nodes);
List(ValueVector&& nodes) noexcept;
List(ValueVectorIt begin, ValueVectorIt end);
List(ValueVectorConstIt begin, ValueVectorConstIt end);
List(const List& that, ValuePtr meta);
@ -154,6 +164,7 @@ class Vector final : public Collection {
public:
Vector() = default;
Vector(const ValueVector& nodes);
Vector(ValueVector&& nodes) noexcept;
Vector(ValueVectorIt begin, ValueVectorIt end);
Vector(ValueVectorConstIt begin, ValueVectorConstIt end);
Vector(const Vector& that, ValuePtr meta);
@ -229,6 +240,7 @@ private:
class Keyword final : public Value {
public:
Keyword(const std::string& data);
Keyword(int64_t number);
virtual ~Keyword() = default;
virtual bool isKeyword() const override { return true; }
@ -242,8 +254,19 @@ private:
};
// -----------------------------------------
class Numeric : public Value {
public:
virtual ~Numeric() = default;
protected:
Numeric() = default;
virtual bool isNumeric() const override { return true; }
};
// 123
class Number final : public Value {
class Number final : public Numeric {
public:
Number(int64_t number);
virtual ~Number() = default;
@ -258,6 +281,22 @@ private:
const int64_t m_number { 0 };
};
// 123.456
class Decimal final : public Numeric {
public:
Decimal(double decimal);
virtual ~Decimal() = default;
double decimal() const { return m_decimal; }
WITH_NO_META();
private:
virtual bool isDecimal() const override { return true; }
const double m_decimal { 0 };
};
// -----------------------------------------
// true, false, nil
@ -322,11 +361,13 @@ using FunctionType = std::function<ValuePtr(ValueVectorConstIt, ValueVectorConst
class Function final : public Callable {
public:
Function(const std::string& name, FunctionType function);
Function(std::string_view name, std::string_view bindings, std::string_view documentation, FunctionType function);
Function(const Function& that, ValuePtr meta);
virtual ~Function() = default;
const std::string& name() const { return m_name; }
std::string_view name() const { return m_name; }
std::string_view bindings() const { return m_bindings; }
std::string_view documentation() const { return m_documentation; }
FunctionType function() const { return m_function; }
WITH_META(Function);
@ -334,7 +375,9 @@ public:
private:
virtual bool isFunction() const override { return true; }
const std::string m_name;
std::string_view m_name;
std::string_view m_bindings;
std::string_view m_documentation;
const FunctionType m_function;
};
@ -414,9 +457,15 @@ inline bool Value::fastIs<String>() const { return isString(); }
template<>
inline bool Value::fastIs<Keyword>() const { return isKeyword(); }
template<>
inline bool Value::fastIs<Numeric>() const { return isNumeric(); }
template<>
inline bool Value::fastIs<Number>() const { return isNumber(); }
template<>
inline bool Value::fastIs<Decimal>() const { return isDecimal(); }
template<>
inline bool Value::fastIs<Constant>() const { return isConstant(); }

174
src/env/environment.cpp vendored

@ -0,0 +1,174 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <filesystem>
#include <iterator> // std::distance
#include <memory> // std::static_pointer_cast
#include "ruc/file.h"
#include "ruc/format/format.h"
#include "ast.h"
#include "env/environment.h"
#include "error.h"
#include "forward.h"
#include "repl.h"
namespace blaze {
std::vector<FunctionParts> Environment::s_function_parts;
std::vector<std::string> Environment::s_lambdas;
EnvironmentPtr Environment::create()
{
return std::shared_ptr<Environment>(new Environment);
}
EnvironmentPtr Environment::create(EnvironmentPtr outer)
{
auto env = create();
env->m_outer = outer;
return env;
}
EnvironmentPtr Environment::create(const ValuePtr lambda, ValueVector&& arguments)
{
auto lambda_casted = std::static_pointer_cast<Lambda>(lambda);
auto env = create(lambda_casted->env());
auto bindings = lambda_casted->bindings();
auto it = arguments.begin();
for (size_t i = 0; i < bindings.size(); ++i, ++it) {
if (bindings[i] == "&") {
if (i + 2 != bindings.size()) {
Error::the().add(::format("invalid function: {}", lambda));
return nullptr;
}
auto nodes = ValueVector();
for (; it != arguments.end(); ++it) {
nodes.push_back(*it);
}
env->set(bindings[i + 1], makePtr<List>(nodes));
return env;
}
if (it == arguments.end()) {
Error::the().add(::format("wrong number of arguments: {}, {}", lambda, arguments.size()));
return nullptr;
}
env->set(bindings[i], *it);
}
if (it != arguments.end()) {
Error::the().add(::format("wrong number of arguments: {}, {}", lambda, arguments.size()));
return nullptr;
}
return env;
}
// -----------------------------------------
void Environment::loadFunctions()
{
loadCollectionAccess();
loadCollectionConstructor();
loadCollectionModify();
loadCompare();
loadConvert();
loadFormat();
loadMeta();
loadMutable();
loadOperators();
loadOther();
loadPredicate();
loadRepl();
// Load std files
std::filesystem::path std = "./lisp";
if (!std::filesystem::exists(std) || !std::filesystem::is_directory(std)) {
return;
}
s_lambdas.reserve(std::distance(std::filesystem::directory_iterator(std), {}));
for (const auto& entry : std::filesystem::directory_iterator(std)) {
if (!std::filesystem::is_regular_file(entry.path())
|| entry.path().extension().string() != ".bl") {
continue;
}
std::filesystem::path filename = entry.path().filename();
ruc::File file((std / filename).string());
// The init will be added to the front and executed first
if (filename.string() == "init.bl") {
s_lambdas.emplace(s_lambdas.begin(), file.data());
}
else {
s_lambdas.push_back(file.data());
}
}
}
void Environment::registerFunction(FunctionParts function_parts)
{
s_function_parts.push_back(function_parts);
}
void Environment::installFunctions(EnvironmentPtr env)
{
for (const auto& function_parts : s_function_parts) {
env->set(std::string(function_parts.name),
makePtr<Function>(
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)
Repl::eval(Repl::read("(do " + lambda + ")"), env);
}
}
// -----------------------------------------
bool Environment::exists(std::string_view symbol)
{
return m_values.find(std::string(symbol)) != m_values.end();
}
ValuePtr Environment::set(std::string_view symbol, ValuePtr value)
{
if (exists(symbol)) {
m_values.erase(std::string(symbol));
}
m_values.emplace(symbol, value);
return value;
}
ValuePtr Environment::get(std::string_view symbol)
{
if (exists(symbol)) {
return m_values[std::string(symbol)];
}
if (m_outer) {
return m_outer->get(symbol);
}
return nullptr;
}
} // namespace blaze

68
src/env/environment.h vendored

@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <list>
#include <string>
#include <unordered_map>
#include <vector>
#include "ast.h"
#include "forward.h"
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;
// Factory functions instead of constructors because it can fail in the bindings/arguments case
static EnvironmentPtr create();
static EnvironmentPtr create(EnvironmentPtr outer);
static EnvironmentPtr create(const ValuePtr lambda, ValueVector&& arguments);
static void loadFunctions();
static void registerFunction(FunctionParts function_parts);
static void installFunctions(EnvironmentPtr env);
bool exists(std::string_view symbol);
ValuePtr set(std::string_view symbol, ValuePtr value);
ValuePtr get(std::string_view symbol);
private:
Environment() {}
// Outer environment native functions, "Core"
static void loadCollectionAccess();
static void loadCollectionConstructor();
static void loadCollectionModify();
static void loadCompare();
static void loadConvert();
static void loadFormat();
static void loadMeta();
static void loadMutable();
static void loadOperators();
static void loadOther();
static void loadPredicate();
static void loadRepl();
EnvironmentPtr m_outer { nullptr };
std::unordered_map<std::string, ValuePtr> m_values;
static std::vector<FunctionParts> s_function_parts;
static std::vector<std::string> s_lambdas;
};
} // namespace blaze

185
src/env/functions/collection-access.cpp vendored

@ -0,0 +1,185 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstddef> // size_t
#include <memory> // std:static_pointer_cast
#include "ast.h"
#include "env/macro.h"
#include "forward.h"
#include "util.h"
namespace blaze {
void Environment::loadCollectionAccess()
{
// (count '(1 2 3)) -> 3
// (count [1 2 3]) -> 3
// (count {:foo 2 :bar 3}) -> 2
ADD_FUNCTION(
"count",
"",
"",
{
CHECK_ARG_COUNT_IS("count", SIZE(), 1);
size_t result = 0;
if (is<Constant>(begin->get()) && std::static_pointer_cast<Constant>(*begin)->state() == Constant::Nil) {
// result = 0
}
else if (is<Collection>(begin->get())) {
result = std::static_pointer_cast<Collection>(*begin)->size();
}
else if (is<HashMap>(begin->get())) {
result = std::static_pointer_cast<HashMap>(*begin)->size();
}
else {
Error::the().add(::format("wrong argument type: Collection, '{}'", *begin));
return nullptr;
}
// FIXME: Add numeric_limits check for implicit cast: size_t > int64_t
return makePtr<Number>((int64_t)result);
});
// -----------------------------------------
// (first (list 1 2 3)) -> 1
ADD_FUNCTION(
"first",
"",
"",
{
CHECK_ARG_COUNT_IS("first", SIZE(), 1);
if (is<Constant>(begin->get())
&& std::static_pointer_cast<Constant>(*begin)->state() == Constant::Nil) {
return makePtr<Constant>();
}
VALUE_CAST(collection, Collection, (*begin));
return (collection->empty()) ? makePtr<Constant>() : collection->front();
});
// (nth (list 1 2 3) 0) -> 1
ADD_FUNCTION(
"nth",
"",
"",
{
CHECK_ARG_COUNT_IS("nth", SIZE(), 2);
VALUE_CAST(collection, Collection, (*begin));
VALUE_CAST(number_node, Number, (*(begin + 1)));
auto collection_nodes = collection->nodesRead();
auto index = static_cast<size_t>(number_node->number());
if (number_node->number() < 0 || index >= collection_nodes.size()) {
Error::the().add("index is out of range");
return nullptr;
}
return collection_nodes[index];
});
// (rest (list 1 2 3)) -> (2 3)
ADD_FUNCTION(
"rest",
"",
"",
{
CHECK_ARG_COUNT_IS("rest", SIZE(), 1);
if (is<Constant>(begin->get())
&& std::static_pointer_cast<Constant>(*begin)->state() == Constant::Nil) {
return makePtr<List>();
}
VALUE_CAST(collection, Collection, (*begin));
return makePtr<List>(collection->rest());
});
// -----------------------------------------
// (get {:kw "value"} :kw) -> "value"
ADD_FUNCTION(
"get",
"",
"",
{
CHECK_ARG_COUNT_AT_LEAST("get", SIZE(), 1);
if (is<Constant>(begin->get())
&& std::static_pointer_cast<Constant>(*begin)->state() == Constant::Nil) {
return makePtr<Constant>();
}
VALUE_CAST(hash_map, HashMap, (*begin));
begin++;
if (SIZE() == 0) {
return makePtr<Constant>();
}
auto result = hash_map->get(*begin);
return (result) ? result : makePtr<Constant>();
});
// (keys {"foo" 3 :bar 5}) -> ("foo" :bar)
ADD_FUNCTION(
"keys",
"",
"",
{
CHECK_ARG_COUNT_AT_LEAST("keys", SIZE(), 1);
VALUE_CAST(hash_map, HashMap, (*begin));
size_t count = hash_map->size();
auto nodes = ValueVector(count);
size_t i = 0;
auto elements = hash_map->elements();
for (auto pair : elements) {
if (pair.first.front() == 0x7f) { // 127
nodes.at(i) = makePtr<Keyword>(pair.first.substr(1));
}
else {
nodes.at(i) = makePtr<String>(pair.first);
}
i++;
}
return makePtr<List>(nodes);
});
// (vals {"foo" 3 :bar 5}) -> (3 5)
ADD_FUNCTION(
"vals",
"",
"",
{
CHECK_ARG_COUNT_AT_LEAST("vals", SIZE(), 1);
VALUE_CAST(hash_map, HashMap, (*begin));
size_t count = hash_map->size();
auto nodes = ValueVector(count);
size_t i = 0;
auto elements = hash_map->elements();
for (auto pair : elements) {
nodes.at(i) = pair.second;
i++;
}
return makePtr<List>(nodes);
});
}
} // namespace blaze

132
src/env/functions/collection-constructor.cpp vendored

@ -0,0 +1,132 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstddef> // size_t
#include <memory> // std:static_pointer_cast
#include "ast.h"
#include "env/macro.h"
#include "forward.h"
#include "util.h"
namespace blaze {
void Environment::loadCollectionConstructor()
{
// (list 1 2) -> (1 2)
ADD_FUNCTION(
"list",
"",
"",
{
return makePtr<List>(begin, end);
});
// (make-list 4 nil) -> (nil nil nil nil)
ADD_FUNCTION(
"make-list",
"",
"",
{
CHECK_ARG_COUNT_IS("make-list", SIZE(), 2);
VALUE_CAST(number, Number, (*begin));
auto count = static_cast<size_t>(number->number() < 0 ? 0 : number->number());
auto value = *std::next(begin);
auto nodes = ValueVector(count);
if (is<Atom>(value.get())) {
auto atom = std::static_pointer_cast<Atom>(value);
for (size_t i = 0; i < count; ++i) {
nodes[i] = makePtr<Atom>(atom);
}
}
// else if (is<Collection>(value.get())) {
// for (size_t i = 0; i < count; ++i) {
// auto nodes = std::static_pointer_cast<Collection>(value)->nodesCopy();
// if (is<Vector>(value.get())) {
// makePtr<Vector>(nodes);
// continue;
// }
// nodes[i] = makePtr<List>(nodes);
// }
// }
// else if (is<Constant>(value.get())) {
// for (size_t i = 0; i < count; ++i) {
// auto constant = std::static_pointer_cast<Constant>(value);
// nodes[i] = makePtr<Constant>(constant);
// }
// }
// TODO:
// Atom
// Collection
// Constant
// Function
// HashMap
// Keyword
// Lambda
// List
// Macro
// Number
// String
// Symbol
// Vector
return makePtr<List>(std::move(nodes));
});
// -----------------------------------------
// (vec (list 1 2 3))
ADD_FUNCTION(
"vec",
"",
"",
{
CHECK_ARG_COUNT_IS("vec", SIZE(), 1);
if (is<Vector>(begin->get())) {
return *begin;
}
VALUE_CAST(collection, Collection, (*begin));
return makePtr<Vector>(collection->nodesCopy());
});
// (vector 1 2 3) -> [1 2 3]
ADD_FUNCTION(
"vector",
"",
"",
{
auto result = makePtr<Vector>();
return makePtr<Vector>(begin, end);
});
// -----------------------------------------
// (hash-map "foo" 5 :bar 10) -> {"foo" 5 :bar 10}
ADD_FUNCTION(
"hash-map",
"",
"",
{
CHECK_ARG_COUNT_EVEN("hash-map", SIZE());
Elements elements;
for (auto it = begin; it != end; std::advance(it, 2)) {
const ValuePtr& value = *(std::next(it)); // temporary instance to get around const
elements.insert_or_assign(HashMap::getKeyString(*it), value);
}
return makePtr<HashMap>(elements);
});
}
} // namespace blaze

287
src/env/functions/collection-modify.cpp vendored

@ -0,0 +1,287 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <algorithm> // std::copy, std::reverse_copy
#include <cstddef> // size_t
#include <memory> // std::static_pointer_cast
#include "ast.h"
#include "env/environment.h"
#include "env/macro.h"
#include "forward.h"
#include "repl.h"
#include "util.h"
namespace blaze {
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);
auto callable = *begin;
IS_VALUE(Callable, callable);
VALUE_CAST(collection, Collection, (*std::prev(end)));
auto arguments = ValueVector(begin + 1, end - 1);
arguments.reserve(arguments.size() + collection->size());
// Append list nodes to the argument leftovers
auto nodes = collection->nodesRead();
for (const auto& node : nodes) {
arguments.push_back(node);
}
ValuePtr value = nullptr;
if (is<Function>(callable.get())) {
auto function = std::static_pointer_cast<Function>(callable)->function();
value = function(arguments.begin(), arguments.end());
}
else {
auto lambda = std::static_pointer_cast<Lambda>(callable);
value = Repl::eval(lambda->body(), Environment::create(lambda, std::move(arguments)));
}
return value;
});
// (cons 1 (list 2 3))
ADD_FUNCTION(
"cons",
"",
"",
{
CHECK_ARG_COUNT_IS("cons", SIZE(), 2);
ValuePtr first = *begin;
begin++;
VALUE_CAST(collection, Collection, (*begin));
const auto& collection_nodes = collection->nodesRead();
auto result_nodes = ValueVector(collection_nodes.size() + 1);
result_nodes.at(0) = first;
std::copy(collection_nodes.begin(), collection_nodes.end(), result_nodes.begin() + 1);
return makePtr<List>(result_nodes);
});
// (concat (list 1) (list 2 3)) -> (1 2 3)
ADD_FUNCTION(
"concat",
"",
"",
{
size_t count = 0;
for (auto it = begin; it != end; ++it) {
VALUE_CAST(collection, Collection, (*it));
count += collection->size();
}
auto result_nodes = ValueVector(count);
size_t offset = 0;
for (auto it = begin; it != end; ++it) {
const auto& collection_nodes = std::static_pointer_cast<Collection>(*it)->nodesRead();
std::copy(collection_nodes.begin(), collection_nodes.end(), result_nodes.begin() + offset);
offset += collection_nodes.size();
}
return makePtr<List>(result_nodes);
});
// (conj '(1 2 3) 4 5 6) -> (6 5 4 1 2 3)
// (conj [1 2 3] 4 5 6) -> [1 2 3 4 5 6]
ADD_FUNCTION(
"conj",
"",
"",
{
CHECK_ARG_COUNT_AT_LEAST("conj", SIZE(), 1);
VALUE_CAST(collection, Collection, (*begin));
begin++;
const auto& collection_nodes = collection->nodesRead();
size_t collection_count = collection_nodes.size();
size_t argument_count = SIZE();
auto nodes = ValueVector(argument_count + collection_count);
if (is<List>(collection.get())) {
std::reverse_copy(begin, end, nodes.begin());
std::copy(collection_nodes.begin(), collection_nodes.end(), nodes.begin() + argument_count);
return makePtr<List>(nodes);
}
std::copy(collection_nodes.begin(), collection_nodes.end(), nodes.begin());
std::copy(begin, end, nodes.begin() + collection_count);
return makePtr<Vector>(nodes);
});
// (map (fn* (x) (* x 2)) (list 1 2 3)) -> (2 4 6)
ADD_FUNCTION(
"map",
"",
"",
{
CHECK_ARG_COUNT_IS("map", SIZE(), 2);
VALUE_CAST(callable, Callable, (*begin));
VALUE_CAST(collection, Collection, (*(begin + 1)));
size_t count = collection->size();
auto nodes = ValueVector(count);
if (is<Function>(callable.get())) {
auto function = std::static_pointer_cast<Function>(callable)->function();
for (size_t i = 0; i < count; ++i) {
nodes.at(i) = function(collection->begin() + i, collection->begin() + i + 1);
}
}
else {
auto lambda = std::static_pointer_cast<Lambda>(callable);
auto collection_nodes = collection->nodesRead();
for (size_t i = 0; i < count; ++i) {
nodes.at(i) = (Repl::eval(lambda->body(), Environment::create(lambda, { collection_nodes[i] })));
}
}
return makePtr<List>(nodes);
});
// (set-nth (list 1 2 3) 1 "foo") -> (1 "foo" 3)
ADD_FUNCTION(
"set-nth",
"",
"",
{
CHECK_ARG_COUNT_IS("set-nth-element", SIZE(), 3);
VALUE_CAST(collection, Collection, (*begin));
VALUE_CAST(number_node, Number, (*(begin + 1)));
auto index = static_cast<size_t>(number_node->number() < 0 ? 0 : number_node->number());
auto value = *(begin + 2);
auto collection_nodes = collection->nodesCopy();
if (index >= collection->size()) { // Enlarge list if index out of bounds
collection_nodes.resize(index + 1, makePtr<Constant>());
}
collection_nodes[index] = value;
if (is<Vector>(begin->get())) {
return makePtr<Vector>(collection_nodes);
}
return makePtr<List>(collection_nodes);
});
// (seq '(1 2 3)) -> (1 2 3)
// (seq [1 2 3]) -> (1 2 3)
// (seq "foo") -> ("f" "o" "o")
ADD_FUNCTION(
"seq",
"",
"",
{
CHECK_ARG_COUNT_IS("seq", SIZE(), 1);
auto front = *begin;
Value* front_raw_ptr = front.get();
if (is<Constant>(front_raw_ptr) && std::static_pointer_cast<Constant>(front)->state() == Constant::Nil) {
return makePtr<Constant>();
}
if (is<Collection>(front_raw_ptr)) {
auto collection = std::static_pointer_cast<Collection>(front);
if (collection->empty()) {
return makePtr<Constant>();
}
if (is<List>(front_raw_ptr)) {
return front;
}
return makePtr<List>(collection->nodesCopy());
}
if (is<String>(front_raw_ptr)) {
auto string = std::static_pointer_cast<String>(front);
if (string->empty()) {
return makePtr<Constant>();
}
size_t count = string->size();
auto nodes = ValueVector(count);
const auto& data = string->data();
for (size_t i = 0; i < count; ++i) {
nodes.at(i) = makePtr<String>(data[i]);
}
return makePtr<List>(nodes);
}
Error::the().add(::format("wrong argument type: Collection or String, {}", front));
return nullptr;
});
// -----------------------------------------
// (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);
VALUE_CAST(hash_map, HashMap, (*begin));
begin++;
CHECK_ARG_COUNT_EVEN("assoc", SIZE());
Elements elements(hash_map->elements());
for (auto it = begin; it != end; std::advance(it, 2)) {
const ValuePtr& value = *(std::next(it)); // temporary instance to get around const
elements.insert_or_assign(HashMap::getKeyString(*it), value);
}
return makePtr<HashMap>(elements);
});
// (dissoc {:a 1 :b 2 :c 3} :a :c :d) -> {:b 2}
ADD_FUNCTION(
"dissoc",
"",
"",
{
CHECK_ARG_COUNT_AT_LEAST("dissoc", SIZE(), 1);
VALUE_CAST(hash_map, HashMap, (*begin));
begin++;
Elements elements(hash_map->elements());
for (auto it = begin; it != end; ++it) {
elements.erase(HashMap::getKeyString(*it));
}
return makePtr<HashMap>(elements);
});
}
} // namespace blaze

166
src/env/functions/compare.cpp vendored

@ -0,0 +1,166 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstdint> // int64_t
#include <functional> // std::function
#include <memory> // std::static_pointer_cast
#include "ast.h"
#include "env/macro.h"
#include "util.h"
namespace blaze {
void Environment::loadCompare()
{
#define NUMBER_COMPARE(operator) \
{ \
CHECK_ARG_COUNT_AT_LEAST(#operator, SIZE(), 2); \
\
bool result = true; \
\
int64_t number = 0; \
double decimal = 0; \
bool current_numeric_is_number = false; \
\
/* Start with the first number */ \
IS_VALUE(Numeric, (*begin)); \
if (is<Number>(begin->get())) { \
number = std::static_pointer_cast<Number>(*begin)->number(); \
current_numeric_is_number = true; \
} \
else { \
decimal = std::static_pointer_cast<Decimal>(*begin)->decimal(); \