Compare commits
118
Commits
94dcd4c50e
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79ede83a99 | ||
|
|
287c2457e7 | ||
|
|
092ada8479 | ||
|
|
b68839902c | ||
|
|
f87eb4d934 | ||
|
|
11f0553b5a | ||
|
|
705e80ad6b | ||
|
|
4cc2acc8a0 | ||
|
|
f5dc1168eb | ||
|
|
536e55e75a | ||
|
|
25871cd5d5 | ||
|
|
929fb5d645 | ||
|
|
9db041946e | ||
|
|
b74f3448b2 | ||
|
|
b727f7147e | ||
|
|
c6c6d69e73 | ||
|
|
bb6f3e7496 | ||
|
|
1915621427 | ||
|
|
9895195410 | ||
|
|
e8206d762c | ||
|
|
b65482eb68 | ||
|
|
67b982fd4c | ||
|
|
d3a50abfbc | ||
|
|
80b25f8c21 | ||
|
|
0d43512ea9 | ||
|
|
fa4bd63dca | ||
|
|
3aa99d0045 | ||
|
|
a099e955cf | ||
|
|
63a19170ef | ||
|
|
cf8e5dfc66 | ||
|
|
733d84c124 | ||
|
|
ce0443a20e | ||
|
|
6e6479bd14 | ||
|
|
453ca1f796 | ||
|
|
534d80c35d | ||
|
|
099cda8b86 | ||
|
|
4fff2b9aa2 | ||
|
|
63c31d7ccf | ||
|
|
12d6b8ec88 | ||
|
|
f796f9c685 | ||
|
|
86e1a630b8 | ||
|
|
d34ab1efab | ||
|
|
24eab1034e | ||
|
|
6e0fe3a2f5 | ||
|
|
7d91ef9198 | ||
|
|
76eb4204be | ||
|
|
fc76da4ced | ||
|
|
88d9253219 | ||
|
|
fa79f1296a | ||
|
|
bb4ccc5822 | ||
|
|
8aa540c4e5 | ||
|
|
e09f168d4f | ||
|
|
6980d28b36 | ||
|
|
2a16f5ddf5 | ||
|
|
f89e73d44a | ||
|
|
c8b1e6dc81 | ||
|
|
c279f9111e | ||
|
|
2b260d7b43 | ||
|
|
27d6e24243 | ||
|
|
b4742ef380 | ||
|
|
082a029957 | ||
|
|
80e0437a5c | ||
|
|
ba7281b6f1 | ||
|
|
b9feb8e8b6 | ||
|
|
e39862fe9f | ||
|
|
93af6151fa | ||
|
|
9b14ea974a | ||
|
|
25d6f45329 | ||
|
|
835669c4eb | ||
|
|
22920cb810 | ||
|
|
3d5a2e3fbe | ||
|
|
088c5f0248 | ||
|
|
03918b036d | ||
|
|
5f49f08eea | ||
|
|
ed9fa1698e | ||
|
|
f35fea9fa4 | ||
|
|
6e1557ff8c | ||
|
|
21f0d8fbd8 | ||
|
|
4d3c2a4ca2 | ||
|
|
7c62d65d72 | ||
|
|
f0f4ba45b0 | ||
|
|
826af24561 | ||
|
|
c53c0af5a2 | ||
|
|
14367fa5a7 | ||
|
|
e47b338984 | ||
|
|
30b120befc | ||
|
|
90d6ff7d18 | ||
|
|
a60859acc4 | ||
|
|
1e76d4599a | ||
|
|
bbced6f487 | ||
|
|
38d8daa9d0 | ||
|
|
35a32678d0 | ||
|
|
de2a207fcb | ||
|
|
9c1c5114a9 | ||
|
|
c1e4b6c6d1 | ||
|
|
6573ac0b22 | ||
|
|
424bbcc834 | ||
|
|
f132397e15 | ||
|
|
58584f5bba | ||
|
|
f4e388716d | ||
|
|
aba70beeb3 | ||
|
|
9bbf238c34 | ||
|
|
441fdc0564 | ||
|
|
d772495f1a | ||
|
|
cc735a7a11 | ||
|
|
a92eede277 | ||
|
|
9fa6314378 | ||
|
|
9271b9fe01 | ||
|
|
6c12b199e8 | ||
|
|
17fddc1cf4 | ||
|
|
c6ea42bc5d | ||
|
|
5c5a766b7e | ||
|
|
da0b0a91a6 | ||
|
|
b51a3bf15b | ||
|
|
27e584ea84 | ||
|
|
0fea075953 | ||
|
|
f6f8207e1a | ||
|
|
eedfe53b96 |
@@ -14,10 +14,12 @@ AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
IndentPPDirectives: BeforeHash
|
||||
RequiresClausePosition: SingleLine
|
||||
|
||||
BraceWrapping:
|
||||
AfterEnum: false
|
||||
|
||||
+61
-16
@@ -11,6 +11,7 @@ endif()
|
||||
# Options
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
|
||||
option(BLAZE_BUILD_EXAMPLES "Build the Blaze example programs" ${BLAZE_STANDALONE})
|
||||
option(BLAZE_BUILD_TESTS "Build the Blaze test programs" ${BLAZE_STANDALONE})
|
||||
|
||||
# ------------------------------------------
|
||||
|
||||
@@ -55,35 +56,79 @@ endif()
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# ------------------------------------------
|
||||
# Library
|
||||
# Dependencies
|
||||
|
||||
add_subdirectory("vendor/ruc")
|
||||
|
||||
# ------------------------------------------
|
||||
# Application target
|
||||
# Library target
|
||||
|
||||
# Define source files
|
||||
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
|
||||
file(GLOB_RECURSE LIBRARY_SOURCES "src/*.cpp")
|
||||
|
||||
add_executable(${PROJECT} ${PROJECT_SOURCES})
|
||||
target_include_directories(${PROJECT} PRIVATE
|
||||
add_library(${PROJECT} ${LIBRARY_SOURCES})
|
||||
target_include_directories(${PROJECT} PUBLIC
|
||||
"src")
|
||||
target_link_libraries(${PROJECT} ruc)
|
||||
target_link_libraries(${PROJECT} readline ruc)
|
||||
|
||||
# ------------------------------------------
|
||||
# Execute target
|
||||
# Std target
|
||||
|
||||
add_custom_target(run
|
||||
COMMAND ${PROJECT})
|
||||
add_dependencies(run ${PROJECT})
|
||||
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)
|
||||
|
||||
# ------------------------------------------
|
||||
# Example target
|
||||
|
||||
if (BLAZE_BUILD_EXAMPLES)
|
||||
add_subdirectory("example")
|
||||
endif()
|
||||
|
||||
# ------------------------------------------
|
||||
# Test targets
|
||||
|
||||
add_custom_target(test0
|
||||
COMMAND env STEP=step0_repl MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step0_repl.mal -- ./${PROJECT})
|
||||
add_dependencies(test0 ${PROJECT})
|
||||
if (BLAZE_BUILD_TESTS)
|
||||
function(make_test_target target_name step_name)
|
||||
add_custom_target(${target_name}
|
||||
COMMAND ../vendor/mal/runtest.py --deferrable --optional ../tests/${step_name}.mal -- ./${PROJECT})
|
||||
add_dependencies(${target_name} ${PROJECT})
|
||||
endfunction()
|
||||
|
||||
add_custom_target(test1
|
||||
COMMAND env STEP=step1_read_print MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step1_read_print.mal -- ./${PROJECT})
|
||||
add_dependencies(test1 ${PROJECT})
|
||||
make_test_target("test0" "step0_repl")
|
||||
make_test_target("test1" "step1_read_print")
|
||||
make_test_target("test2" "step2_eval")
|
||||
make_test_target("test3" "step3_env")
|
||||
make_test_target("test4" "step4_if_fn_do")
|
||||
make_test_target("test5" "step5_tco")
|
||||
make_test_target("test6" "step6_file")
|
||||
make_test_target("test7" "step7_quote")
|
||||
make_test_target("test8" "step8_macros")
|
||||
make_test_target("test9" "step9_try")
|
||||
make_test_target("testA" "stepA_mal")
|
||||
|
||||
function(make_host_test_target target_name step_name)
|
||||
add_custom_target(${target_name}
|
||||
COMMAND ../vendor/mal/runtest.py --deferrable --optional ../tests/${step_name}.mal -- ./${PROJECT} ../mal/${step_name}.mal)
|
||||
add_dependencies(${target_name} ${PROJECT})
|
||||
endfunction()
|
||||
|
||||
make_host_test_target("host_test0" "step0_repl")
|
||||
make_host_test_target("host_test1" "step1_read_print")
|
||||
make_host_test_target("host_test2" "step2_eval")
|
||||
make_host_test_target("host_test3" "step3_env")
|
||||
make_host_test_target("host_test4" "step4_if_fn_do")
|
||||
# make_host_test_target("host_test5" "step5_tco") # disabled
|
||||
make_host_test_target("host_test6" "step6_file")
|
||||
make_host_test_target("host_test7" "step7_quote")
|
||||
make_host_test_target("host_test8" "step8_macros")
|
||||
make_host_test_target("host_test9" "step9_try")
|
||||
make_host_test_target("host_testA" "stepA_mal")
|
||||
|
||||
add_custom_target(perf
|
||||
COMMAND ./${PROJECT} ../tests/perf1.mal
|
||||
COMMAND ./${PROJECT} ../tests/perf2.mal
|
||||
COMMAND ./${PROJECT} ../tests/perf3.mal)
|
||||
add_dependencies(perf ${PROJECT})
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
MIT License
|
||||
|
||||
Copyright (C) 2023 Riyyi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#+TITLE: blaze lisp
|
||||
#+AUTHOR: Riyyi
|
||||
#+LANGUAGE: en
|
||||
#+OPTIONS: toc:nil
|
||||
|
||||
blaze lisp, written in C++20.
|
||||
|
||||
** Usage
|
||||
|
||||
*** Run the REPL
|
||||
|
||||
#+BEGIN_SRC shell-script
|
||||
$ make run
|
||||
#+END_SRC
|
||||
|
||||
*** Run mal tests
|
||||
|
||||
#+BEGIN_SRC shell-script
|
||||
$ make test0 | less
|
||||
#+END_SRC
|
||||
|
||||
** Download
|
||||
|
||||
*** Clone
|
||||
|
||||
#+BEGIN_SRC shell-script
|
||||
$ git clone https://github.com/riyyi/blaze
|
||||
$ cd blaze
|
||||
$ git submodule update --init --recursive
|
||||
#+END_SRC
|
||||
Or
|
||||
#+BEGIN_SRC shell-script
|
||||
$ git clone --recursive https://github.com/riyyi/blaze
|
||||
#+END_SRC
|
||||
|
||||
*** Update
|
||||
|
||||
#+BEGIN_SRC shell-script
|
||||
$ git pull
|
||||
$ git submodule update --recursive
|
||||
#+END_SRC
|
||||
|
||||
** Build instructions
|
||||
|
||||
#+BEGIN_SRC shell-script
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake .. && make
|
||||
#+END_SRC
|
||||
@@ -0,0 +1 @@
|
||||
file(COPY ${CMAKE_CURRENT_LIST_DIR}/../lisp DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
build/compile_commands.json
|
||||
@@ -0,0 +1,25 @@
|
||||
# ------------------------------------------
|
||||
# User config between these lines
|
||||
|
||||
# Set project name
|
||||
set(EXAMPLE "repl")
|
||||
|
||||
# ------------------------------------------
|
||||
|
||||
project(${EXAMPLE} CXX)
|
||||
|
||||
# Define game source files
|
||||
file(GLOB_RECURSE EXAMPLE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
|
||||
|
||||
add_executable(${EXAMPLE} ${EXAMPLE_SOURCES})
|
||||
target_include_directories(${EXAMPLE} PRIVATE
|
||||
"src")
|
||||
target_link_libraries(${EXAMPLE} ${PROJECT})
|
||||
|
||||
# ------------------------------------------
|
||||
|
||||
# Add 'make run' target
|
||||
add_custom_target(run
|
||||
COMMAND ${EXAMPLE} -c
|
||||
DEPENDS ${PROJECT}
|
||||
WORKING_DIRECTORY "..")
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <csignal> // std::signal
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "ruc/argparser.h"
|
||||
#include "ruc/format/color.h"
|
||||
#include "ruc/format/print.h"
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/repl.h"
|
||||
#include "blaze/settings.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
static auto cleanup(int signal) -> void
|
||||
{
|
||||
print("\033[0m\n");
|
||||
Repl::cleanup();
|
||||
std::exit(signal);
|
||||
}
|
||||
|
||||
auto main(int argc, char* argv[]) -> int
|
||||
{
|
||||
bool dump_lexer = false;
|
||||
bool dump_reader = false;
|
||||
bool pretty_print = false;
|
||||
std::string_view history_path = "~/.blaze-history";
|
||||
std::vector<std::string> arguments;
|
||||
|
||||
// CLI arguments
|
||||
ruc::ArgParser arg_parser;
|
||||
arg_parser.addOption(dump_lexer, 'l', "dump-lexer", nullptr, nullptr);
|
||||
arg_parser.addOption(dump_reader, 'r', "dump-reader", nullptr, nullptr);
|
||||
arg_parser.addOption(pretty_print, 'c', "color", nullptr, nullptr);
|
||||
arg_parser.addOption(history_path, 'h', "history-path", nullptr, nullptr, nullptr, ruc::ArgParser::Required::Yes);
|
||||
// TODO: Add overload for addArgument(std::vector<std::string_view>)
|
||||
arg_parser.addArgument(arguments, "arguments", nullptr, nullptr, ruc::ArgParser::Required::No);
|
||||
arg_parser.parse(argc, argv);
|
||||
|
||||
Repl::init();
|
||||
|
||||
// Signal callbacks
|
||||
std::signal(SIGINT, cleanup);
|
||||
std::signal(SIGTERM, cleanup);
|
||||
|
||||
// Set settings
|
||||
g_outer_env->set("*DUMP-LEXER*", makePtr<Constant>(dump_lexer));
|
||||
g_outer_env->set("*DUMP-READER*", makePtr<Constant>(dump_reader));
|
||||
g_outer_env->set("*PRETTY-PRINT*", makePtr<Constant>(pretty_print));
|
||||
|
||||
Repl::makeArgv(g_outer_env, arguments);
|
||||
|
||||
if (arguments.size() > 0) {
|
||||
Repl::rep(format("(load-file \"{}\")", arguments.front()), g_outer_env);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Repl::rep("(println (str \"Blaze [\" *host-language* \"]\"))", g_outer_env);
|
||||
|
||||
g_readline = Readline(pretty_print, history_path);
|
||||
|
||||
std::string input;
|
||||
while (g_readline.get(input)) {
|
||||
std::string output = Repl::rep(input, g_outer_env);
|
||||
if (output.length() > 0) {
|
||||
print("{}\n", output);
|
||||
}
|
||||
}
|
||||
|
||||
if (pretty_print) {
|
||||
print("\033[0m");
|
||||
}
|
||||
|
||||
Repl::cleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
auto main(int argc, char* argv[]) -> int
|
||||
{
|
||||
return blaze::main(argc, argv);
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
(def *host-language* "C++")
|
||||
|
||||
;; Local Variables:
|
||||
;; eval: (emacs-lisp-mode)
|
||||
;; End:
|
||||
@@ -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:
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdint> // int64_t
|
||||
|
||||
#include "ast.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
Collection::~Collection()
|
||||
{
|
||||
for (auto node : m_nodes) {
|
||||
delete node;
|
||||
}
|
||||
}
|
||||
|
||||
void Collection::addNode(ASTNode* node)
|
||||
{
|
||||
m_nodes.push_back(node);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
String::String(const std::string& data)
|
||||
: m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Number::Number(int64_t number)
|
||||
: m_number(number)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Symbol::Symbol(const std::string& symbol)
|
||||
: m_symbol(symbol)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // int64_t
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <typeinfo> // typeid
|
||||
#include <vector>
|
||||
|
||||
namespace blaze {
|
||||
|
||||
class ASTNode {
|
||||
public:
|
||||
virtual ~ASTNode() = default;
|
||||
|
||||
std::string className() const { return typeid(*this).name(); }
|
||||
|
||||
template<typename T>
|
||||
bool fastIs() const = delete;
|
||||
|
||||
virtual bool isCollection() const { return false; }
|
||||
virtual bool isVector() const { return false; }
|
||||
virtual bool isHashMap() const { return false; }
|
||||
virtual bool isList() const { return false; }
|
||||
virtual bool isString() const { return false; }
|
||||
virtual bool isNumber() const { return false; }
|
||||
virtual bool isSpecialSymbol() const { return false; }
|
||||
virtual bool isSymbol() const { return false; }
|
||||
|
||||
protected:
|
||||
ASTNode() {}
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class Collection : public ASTNode {
|
||||
public:
|
||||
virtual ~Collection() override;
|
||||
|
||||
virtual bool isCollection() const override { return true; }
|
||||
|
||||
void addNode(ASTNode* node);
|
||||
|
||||
const std::vector<ASTNode*>& nodes() const { return m_nodes; }
|
||||
|
||||
protected:
|
||||
Collection() {}
|
||||
|
||||
private:
|
||||
std::vector<ASTNode*> m_nodes;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// []
|
||||
class Vector final : public Collection {
|
||||
public:
|
||||
Vector() = default;
|
||||
virtual ~Vector() = default;
|
||||
|
||||
virtual bool isCollection() const override { return false; }
|
||||
virtual bool isVector() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// {}
|
||||
class HashMap final : public Collection {
|
||||
public:
|
||||
HashMap() = default;
|
||||
virtual ~HashMap() = default;
|
||||
|
||||
virtual bool isCollection() const override { return false; }
|
||||
virtual bool isHashMap() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// ()
|
||||
class List final : public Collection {
|
||||
public:
|
||||
List() = default;
|
||||
virtual ~List() = default;
|
||||
|
||||
virtual bool isCollection() const override { return false; }
|
||||
virtual bool isList() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// "string"
|
||||
class String final : public ASTNode {
|
||||
public:
|
||||
String(const std::string& data);
|
||||
virtual ~String() = default;
|
||||
|
||||
virtual bool isString() const override { return true; }
|
||||
|
||||
const std::string& data() const { return m_data; }
|
||||
|
||||
private:
|
||||
std::string m_data;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// 123
|
||||
class Number final : public ASTNode {
|
||||
public:
|
||||
Number(int64_t number);
|
||||
virtual ~Number() = default;
|
||||
|
||||
virtual bool isNumber() const override { return true; }
|
||||
|
||||
int64_t number() const { return m_number; }
|
||||
|
||||
private:
|
||||
int64_t m_number { 0 };
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// true, false, nil
|
||||
class SpecialSymbol final : public ASTNode {
|
||||
public:
|
||||
SpecialSymbol();
|
||||
virtual ~SpecialSymbol();
|
||||
|
||||
virtual bool isSpecialSymbol() const override { return true; }
|
||||
|
||||
private:
|
||||
std::string m_symbol;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Other symbols
|
||||
class Symbol final : public ASTNode {
|
||||
public:
|
||||
Symbol(const std::string& symbol);
|
||||
virtual ~Symbol() = default;
|
||||
|
||||
virtual bool isSymbol() const override { return true; }
|
||||
|
||||
std::string symbol() const { return m_symbol; }
|
||||
|
||||
private:
|
||||
std::string m_symbol;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// clang-format off
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Collection>() const { return isCollection(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Vector>() const { return isVector(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<HashMap>() const { return isHashMap(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<List>() const { return isList(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<String>() const { return isString(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Number>() const { return isNumber(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<SpecialSymbol>() const { return isSpecialSymbol(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Symbol>() const { return isSymbol(); }
|
||||
// clang-format on
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdint> // int64_t
|
||||
#include <memory> // std::static_pointer_cast
|
||||
#include <string>
|
||||
#include <utility> // std::move
|
||||
#include <vector>
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/printer.h"
|
||||
#include "blaze/types.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
ValuePtr Value::withMeta(ValuePtr meta) const
|
||||
{
|
||||
return withMetaImpl(meta);
|
||||
}
|
||||
|
||||
ValuePtr Value::meta() const
|
||||
{
|
||||
return (m_meta == nullptr) ? makePtr<Constant>() : m_meta;
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Collection::Collection(const ValueVector& nodes)
|
||||
: m_nodes(nodes)
|
||||
{
|
||||
}
|
||||
|
||||
Collection::Collection(ValueVector&& nodes) noexcept
|
||||
: m_nodes(std::move(nodes))
|
||||
{
|
||||
}
|
||||
|
||||
Collection::Collection(ValueVectorIt begin, ValueVectorIt end)
|
||||
: m_nodes(ValueVector(begin, end))
|
||||
{
|
||||
}
|
||||
|
||||
Collection::Collection(ValueVectorConstIt begin, ValueVectorConstIt end)
|
||||
: m_nodes(ValueVector(begin, end))
|
||||
{
|
||||
}
|
||||
|
||||
Collection::Collection(const Collection& that, ValuePtr meta)
|
||||
: Value(meta)
|
||||
, m_nodes(that.m_nodes)
|
||||
{
|
||||
}
|
||||
|
||||
ValueVector Collection::rest() const
|
||||
{
|
||||
auto start = (m_nodes.size() > 0) ? m_nodes.begin() + 1 : m_nodes.end();
|
||||
return ValueVector(start, m_nodes.end());
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
List::List(const ValueVector& nodes)
|
||||
: Collection(nodes)
|
||||
{
|
||||
}
|
||||
|
||||
List::List(ValueVector&& nodes) noexcept
|
||||
: Collection(std::move(nodes))
|
||||
{
|
||||
}
|
||||
|
||||
List::List(ValueVectorIt begin, ValueVectorIt end)
|
||||
: Collection(begin, end)
|
||||
{
|
||||
}
|
||||
|
||||
List::List(ValueVectorConstIt begin, ValueVectorConstIt end)
|
||||
: Collection(begin, end)
|
||||
{
|
||||
}
|
||||
|
||||
List::List(const List& that, ValuePtr meta)
|
||||
: Collection(that, meta)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Vector::Vector(const ValueVector& nodes)
|
||||
: Collection(nodes)
|
||||
{
|
||||
}
|
||||
|
||||
Vector::Vector(ValueVector&& nodes) noexcept
|
||||
: Collection(std::move(nodes))
|
||||
{
|
||||
}
|
||||
|
||||
Vector::Vector(ValueVectorIt begin, ValueVectorIt end)
|
||||
: Collection(begin, end)
|
||||
{
|
||||
}
|
||||
|
||||
Vector::Vector(ValueVectorConstIt begin, ValueVectorConstIt end)
|
||||
: Collection(begin, end)
|
||||
{
|
||||
}
|
||||
|
||||
Vector::Vector(const Vector& that, ValuePtr meta)
|
||||
: Collection(that, meta)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
HashMap::HashMap(const Elements& elements)
|
||||
: m_elements(elements)
|
||||
{
|
||||
}
|
||||
|
||||
HashMap::HashMap(const HashMap& that, ValuePtr meta)
|
||||
: Value(meta)
|
||||
, m_elements(that.m_elements)
|
||||
{
|
||||
}
|
||||
|
||||
std::string HashMap::getKeyString(ValuePtr key)
|
||||
{
|
||||
if (!is<String>(key.get()) && !is<Keyword>(key.get())) {
|
||||
Error::the().add(::format("wrong argument type: string or keyword, {}", key));
|
||||
return {};
|
||||
}
|
||||
|
||||
return is<String>(key.get())
|
||||
? std::static_pointer_cast<String>(key)->data()
|
||||
: std::static_pointer_cast<Keyword>(key)->keyword();
|
||||
}
|
||||
|
||||
bool HashMap::exists(const std::string& key)
|
||||
{
|
||||
return m_elements.find(key) != m_elements.end();
|
||||
}
|
||||
|
||||
bool HashMap::exists(ValuePtr key)
|
||||
{
|
||||
return exists(getKeyString(key));
|
||||
}
|
||||
|
||||
ValuePtr HashMap::get(const std::string& key)
|
||||
{
|
||||
if (!exists(key)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_elements[key];
|
||||
}
|
||||
|
||||
ValuePtr HashMap::get(ValuePtr key)
|
||||
{
|
||||
return get(getKeyString(key));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
String::String(const std::string& data)
|
||||
: m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
String::String(char character)
|
||||
: m_data(std::string(1, character))
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Keyword::Keyword(const std::string& data)
|
||||
: m_data(std::string(1, 0x7f) + data) // 127
|
||||
{
|
||||
}
|
||||
|
||||
Keyword::Keyword(int64_t number)
|
||||
: m_data(std::string(1, 0x7f) + std::to_string(number)) // 127
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Number::Number(int64_t number)
|
||||
: Numeric()
|
||||
, m_number(number)
|
||||
{
|
||||
}
|
||||
|
||||
Decimal::Decimal(double decimal)
|
||||
: Numeric()
|
||||
, m_decimal(decimal)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Constant::Constant(State state)
|
||||
: m_state(state)
|
||||
{
|
||||
}
|
||||
|
||||
Constant::Constant(bool state)
|
||||
: m_state(state ? Constant::True : Constant::False)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Symbol::Symbol(const std::string& symbol)
|
||||
: m_symbol(symbol)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Callable::Callable(ValuePtr meta)
|
||||
: Value(meta)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
Function::Function(const Function& that, ValuePtr meta)
|
||||
: Callable(meta)
|
||||
, m_name(that.m_name)
|
||||
, m_function(that.m_function)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Lambda::Lambda(const std::vector<std::string>& bindings, ValuePtr body, EnvironmentPtr env)
|
||||
: Callable()
|
||||
, m_bindings(bindings)
|
||||
, m_body(body)
|
||||
, m_env(env)
|
||||
{
|
||||
}
|
||||
|
||||
Lambda::Lambda(const Lambda& that)
|
||||
: Callable()
|
||||
, m_bindings(that.m_bindings)
|
||||
, m_body(that.m_body)
|
||||
, m_env(that.m_env)
|
||||
{
|
||||
}
|
||||
|
||||
Lambda::Lambda(const Lambda& that, ValuePtr meta)
|
||||
: Callable(meta)
|
||||
, m_bindings(that.m_bindings)
|
||||
, m_body(that.m_body)
|
||||
, m_env(that.m_env)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Macro::Macro(const Lambda& that)
|
||||
: Lambda(that)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Atom::Atom(ValuePtr pointer)
|
||||
: m_value(pointer)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Formatter<blaze::ValuePtr>::format(Builder& builder, blaze::ValuePtr value) const
|
||||
{
|
||||
blaze::Printer printer;
|
||||
return Formatter<std::string>::format(builder, printer.printNoErrorCheck(value));
|
||||
}
|
||||
+523
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <concepts> // std::derived_from, std::same_as
|
||||
#include <cstdint> // int64_t, uint8_t
|
||||
#include <functional> // std::function
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory> // std::make_shared, std::shared_ptr
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <typeinfo> // typeid
|
||||
#include <utility> // std::forward
|
||||
#include <vector>
|
||||
|
||||
#include "ruc/format/formatter.h"
|
||||
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/to-from-hashmap.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
template<typename T, typename... Args>
|
||||
std::shared_ptr<T> makePtr(Args&&... args)
|
||||
{
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class Value {
|
||||
public:
|
||||
virtual ~Value() = default;
|
||||
|
||||
virtual ValuePtr withMetaImpl(ValuePtr meta) const = 0;
|
||||
|
||||
ValuePtr withMeta(ValuePtr meta) const;
|
||||
ValuePtr meta() const;
|
||||
|
||||
std::string className() const { return typeid(*this).name(); }
|
||||
|
||||
template<typename T>
|
||||
bool fastIs() const = delete;
|
||||
|
||||
virtual bool isCollection() const { return false; }
|
||||
virtual bool isList() const { return false; }
|
||||
virtual bool isVector() const { return false; }
|
||||
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; }
|
||||
virtual bool isFunction() const { return false; }
|
||||
virtual bool isLambda() const { return false; }
|
||||
virtual bool isMacro() const { return false; }
|
||||
virtual bool isAtom() const { return false; }
|
||||
|
||||
protected:
|
||||
Value() {}
|
||||
|
||||
Value(ValuePtr meta)
|
||||
: m_meta(meta)
|
||||
{
|
||||
}
|
||||
|
||||
ValuePtr m_meta;
|
||||
};
|
||||
|
||||
#define WITH_META(Type) \
|
||||
virtual ValuePtr withMetaImpl(ValuePtr meta) const override \
|
||||
{ \
|
||||
return std::make_shared<Type>(*this, meta); \
|
||||
}
|
||||
|
||||
#define WITH_NO_META() \
|
||||
virtual ValuePtr withMetaImpl(ValuePtr) const override \
|
||||
{ \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
template<typename T>
|
||||
concept IsValue = std::same_as<Value, T> || std::derived_from<T, Value>;
|
||||
|
||||
class Collection : public Value {
|
||||
public:
|
||||
virtual ~Collection() = default;
|
||||
|
||||
// TODO: rename size -> count
|
||||
size_t size() const { return m_nodes.size(); }
|
||||
bool empty() const { return m_nodes.size() == 0; }
|
||||
|
||||
ValuePtr front() const { return m_nodes.front(); }
|
||||
ValueVector rest() const;
|
||||
|
||||
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);
|
||||
|
||||
template<IsValue... Ts>
|
||||
Collection(std::shared_ptr<Ts>... nodes)
|
||||
{
|
||||
m_nodes = { nodes... };
|
||||
}
|
||||
|
||||
private:
|
||||
virtual bool isCollection() const override { return true; }
|
||||
|
||||
ValueVector m_nodes;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// ()
|
||||
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);
|
||||
|
||||
template<IsValue... Ts>
|
||||
List(std::shared_ptr<Ts>... nodes)
|
||||
: Collection(nodes...)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~List() = default;
|
||||
|
||||
WITH_META(List);
|
||||
|
||||
private:
|
||||
virtual bool isList() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// []
|
||||
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);
|
||||
|
||||
template<IsValue... Ts>
|
||||
Vector(std::shared_ptr<Ts>... nodes)
|
||||
: Collection(nodes...)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~Vector() = default;
|
||||
|
||||
WITH_META(Vector);
|
||||
|
||||
private:
|
||||
virtual bool isVector() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
using Elements = std::map<std::string, ValuePtr>;
|
||||
|
||||
// {}
|
||||
class HashMap final : public Value {
|
||||
public:
|
||||
HashMap() = default;
|
||||
HashMap(const Elements& elements);
|
||||
HashMap(const HashMap& that, ValuePtr meta);
|
||||
virtual ~HashMap() = default;
|
||||
|
||||
static HashMapPtr create(const Elements& elements)
|
||||
{
|
||||
return std::make_shared<HashMap>(elements);
|
||||
}
|
||||
|
||||
// Customization Point
|
||||
template<typename T>
|
||||
static HashMapPtr create(T value)
|
||||
{
|
||||
HashMapPtr hash_map;
|
||||
to_hash_map(hash_map, std::forward<T>(value));
|
||||
return hash_map;
|
||||
}
|
||||
|
||||
static std::string getKeyString(ValuePtr key);
|
||||
|
||||
bool exists(const std::string& key);
|
||||
bool exists(ValuePtr key);
|
||||
ValuePtr get(const std::string& key);
|
||||
ValuePtr get(ValuePtr key);
|
||||
const Elements& elements() const { return m_elements; }
|
||||
size_t size() const { return m_elements.size(); }
|
||||
bool empty() const { return m_elements.size() == 0; }
|
||||
|
||||
WITH_META(HashMap);
|
||||
|
||||
private:
|
||||
virtual bool isHashMap() const override { return true; }
|
||||
|
||||
Elements m_elements;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// "string"
|
||||
class String final : public Value {
|
||||
public:
|
||||
String(const std::string& data);
|
||||
String(char character);
|
||||
virtual ~String() = default;
|
||||
|
||||
static ValuePtr create(const std::string& data)
|
||||
{
|
||||
return std::make_shared<String>(data);
|
||||
}
|
||||
|
||||
const std::string& data() const { return m_data; }
|
||||
size_t size() const { return m_data.size(); }
|
||||
bool empty() const { return m_data.empty(); }
|
||||
|
||||
WITH_NO_META();
|
||||
|
||||
private:
|
||||
virtual bool isString() const override { return true; }
|
||||
|
||||
const std::string m_data;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// :keyword
|
||||
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; }
|
||||
|
||||
const std::string& keyword() const { return m_data; }
|
||||
|
||||
WITH_NO_META();
|
||||
|
||||
private:
|
||||
const std::string m_data;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class Numeric : public Value {
|
||||
public:
|
||||
virtual ~Numeric() = default;
|
||||
|
||||
protected:
|
||||
Numeric() = default;
|
||||
|
||||
virtual bool isNumeric() const override { return true; }
|
||||
};
|
||||
|
||||
// 123
|
||||
class Number final : public Numeric {
|
||||
public:
|
||||
Number(int64_t number);
|
||||
virtual ~Number() = default;
|
||||
|
||||
int64_t number() const { return m_number; }
|
||||
|
||||
WITH_NO_META();
|
||||
|
||||
private:
|
||||
virtual bool isNumber() const override { return true; }
|
||||
|
||||
const int64_t m_number { 0 };
|
||||
};
|
||||
|
||||
// 123.456
|
||||
class Decimal final : public Numeric {
|
||||
public:
|
||||
Decimal(double decimal);
|
||||
virtual ~Decimal() = default;
|
||||
|
||||
static ValuePtr create(float value)
|
||||
{
|
||||
return std::make_shared<Decimal>(value);
|
||||
}
|
||||
|
||||
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
|
||||
class Constant final : public Value {
|
||||
public:
|
||||
enum State : uint8_t {
|
||||
Nil,
|
||||
True,
|
||||
False,
|
||||
};
|
||||
|
||||
Constant() = default;
|
||||
Constant(State state);
|
||||
Constant(bool state);
|
||||
virtual ~Constant() = default;
|
||||
|
||||
State state() const { return m_state; }
|
||||
|
||||
WITH_NO_META();
|
||||
|
||||
private:
|
||||
virtual bool isConstant() const override { return true; }
|
||||
|
||||
const State m_state { State::Nil };
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Symbols
|
||||
class Symbol final : public Value {
|
||||
public:
|
||||
Symbol(const std::string& symbol);
|
||||
virtual ~Symbol() = default;
|
||||
|
||||
const std::string& symbol() const { return m_symbol; }
|
||||
|
||||
WITH_NO_META();
|
||||
|
||||
private:
|
||||
virtual bool isSymbol() const override { return true; }
|
||||
|
||||
const std::string m_symbol;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class Callable : public Value {
|
||||
public:
|
||||
virtual ~Callable() = default;
|
||||
|
||||
protected:
|
||||
Callable() = default;
|
||||
Callable(ValuePtr meta);
|
||||
|
||||
private:
|
||||
virtual bool isCallable() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
using FunctionType = std::function<ValuePtr(ValueVectorConstIt, ValueVectorConstIt)>;
|
||||
|
||||
class Function final : public Callable {
|
||||
public:
|
||||
Function(std::string_view name, std::string_view bindings, std::string_view documentation, FunctionType function);
|
||||
Function(const Function& that, ValuePtr meta);
|
||||
virtual ~Function() = default;
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
virtual bool isFunction() const override { return true; }
|
||||
|
||||
std::string_view m_name;
|
||||
std::string_view m_bindings;
|
||||
std::string_view m_documentation;
|
||||
const FunctionType m_function;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class Lambda : public Callable {
|
||||
public:
|
||||
Lambda(const std::vector<std::string>& bindings, ValuePtr body, EnvironmentPtr env);
|
||||
Lambda(const Lambda& that);
|
||||
Lambda(const Lambda& that, ValuePtr meta);
|
||||
virtual ~Lambda() = default;
|
||||
|
||||
const std::vector<std::string>& bindings() const { return m_bindings; }
|
||||
ValuePtr body() const { return m_body; }
|
||||
EnvironmentPtr env() const { return m_env; }
|
||||
|
||||
WITH_META(Lambda);
|
||||
|
||||
private:
|
||||
virtual bool isLambda() const override { return true; }
|
||||
|
||||
const std::vector<std::string> m_bindings;
|
||||
const ValuePtr m_body;
|
||||
const EnvironmentPtr m_env;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class Macro final : public Lambda {
|
||||
public:
|
||||
Macro(const Lambda& that);
|
||||
|
||||
WITH_NO_META();
|
||||
|
||||
private:
|
||||
virtual bool isLambda() const override { return false; }
|
||||
virtual bool isMacro() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class Atom final : public Value {
|
||||
public:
|
||||
Atom() = default;
|
||||
Atom(ValuePtr pointer);
|
||||
virtual ~Atom() = default;
|
||||
|
||||
ValuePtr reset(ValuePtr value) { return m_value = value; }
|
||||
ValuePtr deref() const { return m_value; }
|
||||
|
||||
WITH_NO_META();
|
||||
|
||||
private:
|
||||
virtual bool isAtom() const override { return true; }
|
||||
|
||||
ValuePtr m_value;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// clang-format off
|
||||
template<>
|
||||
inline bool Value::fastIs<Collection>() const { return isCollection(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<List>() const { return isList(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<Vector>() const { return isVector(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<HashMap>() const { return isHashMap(); }
|
||||
|
||||
template<>
|
||||
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(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<Symbol>() const { return isSymbol(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<Callable>() const { return isCallable(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<Function>() const { return isFunction(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<Lambda>() const { return isLambda(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<Macro>() const { return isMacro(); }
|
||||
|
||||
template<>
|
||||
inline bool Value::fastIs<Atom>() const { return isAtom(); }
|
||||
// clang-format on
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
template<>
|
||||
struct ruc::format::Formatter<blaze::ValuePtr> : public Formatter<std::string> {
|
||||
void format(Builder& builder, blaze::ValuePtr value) const;
|
||||
};
|
||||
Vendored
+178
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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 "blaze/ast.h"
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/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()
|
||||
{
|
||||
s_function_parts.clear();
|
||||
s_lambdas.clear();
|
||||
|
||||
loadCollectionAccess();
|
||||
loadCollectionConstructor();
|
||||
loadCollectionModify();
|
||||
loadCompare();
|
||||
loadConvert();
|
||||
loadFormat();
|
||||
loadMath();
|
||||
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
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/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 loadMath();
|
||||
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
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <memory> // std:static_pointer_cast
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/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
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <memory> // std:static_pointer_cast
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/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
@@ -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 "blaze/ast.h"
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/repl.h"
|
||||
#include "blaze/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
|
||||
Vendored
+166
@@ -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 "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/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(); \
|
||||
current_numeric_is_number = false; \
|
||||
} \
|
||||
\
|
||||
/* Skip the first node */ \
|
||||
for (auto it = begin + 1; it != end; ++it) { \
|
||||
IS_VALUE(Numeric, (*it)); \
|
||||
if (is<Number>(*it->get())) { \
|
||||
int64_t it_number = std::static_pointer_cast<Number>(*it)->number(); \
|
||||
if (!((current_numeric_is_number ? number : decimal) operator it_number)) { \
|
||||
result = false; \
|
||||
break; \
|
||||
} \
|
||||
number = it_number; \
|
||||
current_numeric_is_number = true; \
|
||||
} \
|
||||
else { \
|
||||
double it_decimal = std::static_pointer_cast<Decimal>(*it)->decimal(); \
|
||||
if (!((current_numeric_is_number ? number : decimal) operator it_decimal)) { \
|
||||
result = false; \
|
||||
break; \
|
||||
} \
|
||||
decimal = it_decimal; \
|
||||
current_numeric_is_number = false; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
return makePtr<Constant>((result) ? Constant::True : Constant::False); \
|
||||
}
|
||||
|
||||
ADD_FUNCTION("<", "", "", NUMBER_COMPARE(<));
|
||||
ADD_FUNCTION("<=", "", "", NUMBER_COMPARE(<=));
|
||||
ADD_FUNCTION(">", "", "", NUMBER_COMPARE(>));
|
||||
ADD_FUNCTION(">=", "", "", NUMBER_COMPARE(>=));
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// (= 1 1) -> true
|
||||
// (= "foo" "foo") -> true
|
||||
ADD_FUNCTION(
|
||||
"=",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_AT_LEAST("=", SIZE(), 2);
|
||||
|
||||
std::function<bool(ValuePtr, ValuePtr)> equal =
|
||||
[&equal](ValuePtr lhs, ValuePtr rhs) -> bool {
|
||||
if (is<Collection>(lhs.get()) && is<Collection>(rhs.get())) {
|
||||
auto lhs_collection = std::static_pointer_cast<Collection>(lhs);
|
||||
auto rhs_collection = std::static_pointer_cast<Collection>(rhs);
|
||||
|
||||
if (lhs_collection->size() != rhs_collection->size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lhs_it = lhs_collection->begin();
|
||||
auto rhs_it = rhs_collection->begin();
|
||||
for (; lhs_it != lhs_collection->end(); ++lhs_it, ++rhs_it) {
|
||||
if (!equal(*lhs_it, *rhs_it)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is<HashMap>(lhs.get()) && is<HashMap>(rhs.get())) {
|
||||
const auto& lhs_nodes = std::static_pointer_cast<HashMap>(lhs)->elements();
|
||||
const auto& rhs_nodes = std::static_pointer_cast<HashMap>(rhs)->elements();
|
||||
|
||||
if (lhs_nodes.size() != rhs_nodes.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& [key, value] : lhs_nodes) {
|
||||
auto it = rhs_nodes.find(key);
|
||||
if (it == rhs_nodes.cend() || !equal(value, it->second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is<String>(lhs.get()) && is<String>(rhs.get())
|
||||
&& std::static_pointer_cast<String>(lhs)->data() == std::static_pointer_cast<String>(rhs)->data()) {
|
||||
return true;
|
||||
}
|
||||
if (is<Keyword>(lhs.get()) && is<Keyword>(rhs.get())
|
||||
&& std::static_pointer_cast<Keyword>(lhs)->keyword() == std::static_pointer_cast<Keyword>(rhs)->keyword()) {
|
||||
return true;
|
||||
}
|
||||
// clang-format off
|
||||
if (is<Numeric>(lhs.get()) && is<Numeric>(rhs.get())
|
||||
&& (is<Number>(lhs.get())
|
||||
? std::static_pointer_cast<Number>(lhs)->number()
|
||||
: std::static_pointer_cast<Decimal>(lhs)->decimal())
|
||||
== (is<Number>(rhs.get())
|
||||
? std::static_pointer_cast<Number>(rhs)->number()
|
||||
: std::static_pointer_cast<Decimal>(rhs)->decimal())) {
|
||||
return true;
|
||||
}
|
||||
// clang-format on
|
||||
if (is<Constant>(lhs.get()) && is<Constant>(rhs.get())
|
||||
&& std::static_pointer_cast<Constant>(lhs)->state() == std::static_pointer_cast<Constant>(rhs)->state()) {
|
||||
return true;
|
||||
}
|
||||
if (is<Symbol>(lhs.get()) && is<Symbol>(rhs.get())
|
||||
&& std::static_pointer_cast<Symbol>(lhs)->symbol() == std::static_pointer_cast<Symbol>(rhs)->symbol()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
bool result = true;
|
||||
auto it = begin;
|
||||
auto it_next = begin + 1;
|
||||
for (; it_next != end; ++it, ++it_next) {
|
||||
if (!equal(*it, *it_next)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return makePtr<Constant>((result) ? Constant::True : Constant::False);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
Vendored
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <charconv> // std::from_chars, std::to_chars
|
||||
#include <memory>
|
||||
#include <system_error> // std::errc
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadConvert()
|
||||
{
|
||||
// (number-to-string 123) -> "123"
|
||||
ADD_FUNCTION(
|
||||
"number-to-string",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("number-to-string", SIZE(), 1);
|
||||
|
||||
IS_VALUE(Numeric, (*begin));
|
||||
|
||||
char result[32];
|
||||
auto conversion = std::to_chars(result,
|
||||
result + sizeof(result),
|
||||
is<Number>(begin->get())
|
||||
? std::static_pointer_cast<Number>(*begin)->number()
|
||||
: std::static_pointer_cast<Decimal>(*begin)->decimal());
|
||||
if (conversion.ec != std::errc()) {
|
||||
return makePtr<Constant>(Constant::Nil);
|
||||
}
|
||||
|
||||
return makePtr<String>(std::string(result, conversion.ptr - result));
|
||||
});
|
||||
|
||||
// (string-to-char "123") -> 49
|
||||
ADD_FUNCTION(
|
||||
"string-to-char",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("string-to-char", SIZE(), 1);
|
||||
|
||||
VALUE_CAST(string_value, String, (*begin));
|
||||
std::string data = string_value->data();
|
||||
|
||||
return makePtr<Number>(data.c_str()[0]);
|
||||
});
|
||||
|
||||
// (string-to-number "123") -> 123
|
||||
ADD_FUNCTION(
|
||||
"string-to-number",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("string-to-number", SIZE(), 1);
|
||||
|
||||
VALUE_CAST(string_value, String, (*begin));
|
||||
std::string data = string_value->data();
|
||||
|
||||
if (data.find('.') == std::string::npos) {
|
||||
int64_t number;
|
||||
auto conversion_number = std::from_chars(data.c_str(), data.c_str() + data.size(), number);
|
||||
if (conversion_number.ec != std::errc()) {
|
||||
return makePtr<Constant>(Constant::Nil);
|
||||
}
|
||||
|
||||
return makePtr<Number>(number);
|
||||
}
|
||||
|
||||
double decimal;
|
||||
auto conversion_decimal = std::from_chars(data.c_str(), data.c_str() + data.size(), decimal);
|
||||
if (conversion_decimal.ec != std::errc()) {
|
||||
return makePtr<Constant>(Constant::Nil);
|
||||
}
|
||||
|
||||
return makePtr<Decimal>(decimal);
|
||||
});
|
||||
|
||||
#define STRING_TO_COLLECTION(name, type) \
|
||||
{ \
|
||||
CHECK_ARG_COUNT_IS(name, SIZE(), 1); \
|
||||
\
|
||||
VALUE_CAST(string_value, String, (*begin)); \
|
||||
std::string data = string_value->data(); \
|
||||
\
|
||||
ValueVector nodes(data.size()); \
|
||||
for (size_t i = 0; i < data.size(); ++i) { \
|
||||
nodes.at(i) = makePtr<Number>(data.c_str()[i]); \
|
||||
} \
|
||||
\
|
||||
return makePtr<type>(nodes); \
|
||||
}
|
||||
|
||||
// (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));
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
// (symbol "foo") -> foo
|
||||
ADD_FUNCTION(
|
||||
"symbol",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("symbol", SIZE(), 1);
|
||||
|
||||
if (is<Symbol>(begin->get())) {
|
||||
return *begin;
|
||||
}
|
||||
|
||||
VALUE_CAST(string_value, String, (*begin));
|
||||
|
||||
return makePtr<Symbol>(string_value->data());
|
||||
});
|
||||
|
||||
// (keyword "foo") -> :foo
|
||||
// (keyword 123) -> :123
|
||||
ADD_FUNCTION(
|
||||
"keyword",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("keyword", SIZE(), 1);
|
||||
|
||||
if (is<Keyword>(begin->get())) {
|
||||
return *begin;
|
||||
}
|
||||
else if (is<Number>(begin->get())) {
|
||||
VALUE_CAST(number_value, Number, (*begin));
|
||||
|
||||
return makePtr<Keyword>(number_value->number());
|
||||
}
|
||||
|
||||
VALUE_CAST(string_value, String, (*begin));
|
||||
|
||||
return makePtr<Keyword>(string_value->data());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <iterator> // std::next
|
||||
#include <string>
|
||||
|
||||
#include "ruc/format/print.h"
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/printer.h"
|
||||
#include "blaze/reader.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadFormat()
|
||||
{
|
||||
#define PRINTER_STRING(print_readably, concatenation) \
|
||||
{ \
|
||||
std::string result; \
|
||||
\
|
||||
Printer printer; \
|
||||
for (auto it = begin; it != end; ++it) { \
|
||||
result += ::format("{}", printer.printNoErrorCheck(*it, print_readably)); \
|
||||
\
|
||||
if (it != end && std::next(it) != end) { \
|
||||
result += concatenation; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
return makePtr<String>(result); \
|
||||
}
|
||||
|
||||
ADD_FUNCTION("str", "", "", PRINTER_STRING(false, ""));
|
||||
ADD_FUNCTION("pr-str", "", "", PRINTER_STRING(true, " "));
|
||||
|
||||
#define PRINTER_PRINT(print_readably) \
|
||||
{ \
|
||||
Printer printer; \
|
||||
for (auto it = begin; it != end; ++it) { \
|
||||
print("{}", printer.printNoErrorCheck(*it, print_readably)); \
|
||||
\
|
||||
if (it != end && std::next(it) != end) { \
|
||||
print(" "); \
|
||||
} \
|
||||
} \
|
||||
print("\n"); \
|
||||
\
|
||||
return makePtr<Constant>(); \
|
||||
}
|
||||
|
||||
ADD_FUNCTION("prn", "", "", PRINTER_PRINT(true));
|
||||
ADD_FUNCTION("println", "", "", PRINTER_PRINT(false));
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
ADD_FUNCTION(
|
||||
"dump",
|
||||
"arg",
|
||||
"Print AST of the value ARG.",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("dump", SIZE(), 1);
|
||||
|
||||
Reader reader;
|
||||
reader.dump(*begin);
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cmath> // std::sin
|
||||
#include <limits> // sdt::numeric_limits
|
||||
#include <memory> // std::static_pointer_cast
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadMath()
|
||||
{
|
||||
#define MATH_MAX_MIN(variant, limit, operator) \
|
||||
{ \
|
||||
CHECK_ARG_COUNT_AT_LEAST(#variant, SIZE(), 1); \
|
||||
\
|
||||
int64_t number = std::numeric_limits<int64_t>::limit(); \
|
||||
double decimal = std::numeric_limits<double>::limit(); \
|
||||
\
|
||||
for (auto it = begin; it != end; ++it) { \
|
||||
IS_VALUE(Numeric, (*it)); \
|
||||
if (is<Number>(it->get())) { \
|
||||
auto it_numeric = std::static_pointer_cast<Number>(*it)->number(); \
|
||||
if (it_numeric operator number) { \
|
||||
number = it_numeric; \
|
||||
} \
|
||||
} \
|
||||
else { \
|
||||
auto it_numeric = std::static_pointer_cast<Decimal>(*it)->decimal(); \
|
||||
if (it_numeric operator decimal) { \
|
||||
decimal = it_numeric; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
if (number operator decimal) { \
|
||||
return makePtr<Number>(number); \
|
||||
} \
|
||||
\
|
||||
return makePtr<Decimal>(decimal); \
|
||||
}
|
||||
|
||||
ADD_FUNCTION(
|
||||
"max", "number...",
|
||||
"Return largest of all arguments, where NUMBER is a number or decimal.",
|
||||
MATH_MAX_MIN(max, lowest, >));
|
||||
ADD_FUNCTION(
|
||||
"min", "number...",
|
||||
"Return smallest of all arguments, where NUMBER is a number or decimal.",
|
||||
MATH_MAX_MIN(min, max, <));
|
||||
|
||||
#define MATH_COS_SIN(variant) \
|
||||
{ \
|
||||
CHECK_ARG_COUNT_IS(#variant, SIZE(), 1); \
|
||||
\
|
||||
auto value = *begin; \
|
||||
IS_VALUE(Numeric, value); \
|
||||
if (is<Number>(begin->get())) { \
|
||||
return makePtr<Decimal>(std::variant((double)std::static_pointer_cast<Number>(value)->number())); \
|
||||
} \
|
||||
\
|
||||
return makePtr<Decimal>(std::variant(std::static_pointer_cast<Decimal>(value)->decimal())); \
|
||||
}
|
||||
|
||||
ADD_FUNCTION(
|
||||
"cos", "arg",
|
||||
"Return the cosine of ARG.",
|
||||
MATH_COS_SIN(cos));
|
||||
|
||||
ADD_FUNCTION(
|
||||
"sin", "arg",
|
||||
"Return the sine of ARG.",
|
||||
MATH_COS_SIN(sin));
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadMeta()
|
||||
{
|
||||
// (meta [1 2 3])
|
||||
ADD_FUNCTION(
|
||||
"meta",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("meta", SIZE(), 1);
|
||||
|
||||
auto front = *begin;
|
||||
Value* front_raw_ptr = begin->get();
|
||||
|
||||
if (!is<Collection>(front_raw_ptr) && // List / Vector
|
||||
!is<HashMap>(front_raw_ptr) && // HashMap
|
||||
!is<Callable>(front_raw_ptr)) { // Function / Lambda
|
||||
Error::the().add(::format("wrong argument type: Collection, HashMap or Callable, {}", front));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return front->meta();
|
||||
});
|
||||
|
||||
// (with-meta [1 2 3] "some metadata")
|
||||
ADD_FUNCTION(
|
||||
"with-meta",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("with-meta", SIZE(), 2);
|
||||
|
||||
auto front = *begin;
|
||||
Value* front_raw_ptr = begin->get();
|
||||
|
||||
if (!is<Collection>(front_raw_ptr) && // List / Vector
|
||||
!is<HashMap>(front_raw_ptr) && // HashMap
|
||||
!is<Callable>(front_raw_ptr)) { // Function / Lambda
|
||||
Error::the().add(::format("wrong argument type: Collection, HashMap or Callable, {}", front));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return front->withMeta(*(begin + 1));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <algorithm> // std::copy
|
||||
#include <memory> // std::static_pointer_cast
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/repl.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadMutable()
|
||||
{
|
||||
// (atom 1)
|
||||
ADD_FUNCTION(
|
||||
"atom",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("atom", SIZE(), 1);
|
||||
|
||||
return makePtr<Atom>(*begin);
|
||||
});
|
||||
|
||||
// (deref myatom)
|
||||
ADD_FUNCTION(
|
||||
"deref",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("deref", SIZE(), 1);
|
||||
|
||||
VALUE_CAST(atom, Atom, (*begin));
|
||||
|
||||
return atom->deref();
|
||||
});
|
||||
|
||||
// (reset! myatom 2)
|
||||
ADD_FUNCTION(
|
||||
"reset!",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("reset!", SIZE(), 2);
|
||||
|
||||
VALUE_CAST(atom, Atom, (*begin));
|
||||
auto value = *(begin + 1);
|
||||
|
||||
atom->reset(value);
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
// (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);
|
||||
|
||||
VALUE_CAST(atom, Atom, (*begin));
|
||||
|
||||
VALUE_CAST(callable, Callable, (*(begin + 1)));
|
||||
|
||||
// Remove atom and function from the argument list, add atom value
|
||||
begin += 2;
|
||||
auto arguments = ValueVector(SIZE() + 1);
|
||||
arguments[0] = atom->deref();
|
||||
std::copy(begin, end, arguments.begin() + 1);
|
||||
|
||||
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 atom->reset(value);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdint> // int64_t
|
||||
#include <memory> // std::static_pointer_cast
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadOperators()
|
||||
{
|
||||
#define APPLY_NUMBER_OR_DECIMAL(it, apply) \
|
||||
IS_VALUE(Numeric, (*it)); \
|
||||
if (is<Number>(it->get())) { \
|
||||
auto it_numeric = std::static_pointer_cast<Number>(*it)->number(); \
|
||||
do { \
|
||||
apply \
|
||||
} while (0); \
|
||||
} \
|
||||
else { \
|
||||
return_decimal = true; \
|
||||
auto it_numeric = std::static_pointer_cast<Decimal>(*it)->decimal(); \
|
||||
do { \
|
||||
apply \
|
||||
} while (0); \
|
||||
}
|
||||
|
||||
#define RETURN_NUMBER_OR_DECIMAL() \
|
||||
if (!return_decimal) { \
|
||||
return makePtr<Number>(number); \
|
||||
} \
|
||||
return makePtr<Decimal>(decimal);
|
||||
|
||||
ADD_FUNCTION(
|
||||
"+",
|
||||
"number...",
|
||||
"Return the sum of any amount of arguments, where NUMBER is of type number.",
|
||||
{
|
||||
bool return_decimal = false;
|
||||
|
||||
int64_t number = 0;
|
||||
double decimal = 0;
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
APPLY_NUMBER_OR_DECIMAL(it, {
|
||||
number += it_numeric;
|
||||
decimal += it_numeric;
|
||||
});
|
||||
}
|
||||
|
||||
RETURN_NUMBER_OR_DECIMAL();
|
||||
});
|
||||
|
||||
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) {
|
||||
return makePtr<Number>(0);
|
||||
}
|
||||
|
||||
bool return_decimal = false;
|
||||
|
||||
int64_t number = 0;
|
||||
double decimal = 0;
|
||||
|
||||
// Start with the first number
|
||||
APPLY_NUMBER_OR_DECIMAL(begin, {
|
||||
number = it_numeric;
|
||||
decimal = it_numeric;
|
||||
});
|
||||
|
||||
// Return negative on single argument
|
||||
if (length == 1) {
|
||||
number = -number;
|
||||
decimal = -decimal;
|
||||
RETURN_NUMBER_OR_DECIMAL();
|
||||
}
|
||||
|
||||
// Skip the first node
|
||||
for (auto it = begin + 1; it != end; ++it) {
|
||||
APPLY_NUMBER_OR_DECIMAL(it, {
|
||||
number -= it_numeric;
|
||||
decimal -= it_numeric;
|
||||
});
|
||||
}
|
||||
|
||||
RETURN_NUMBER_OR_DECIMAL();
|
||||
});
|
||||
|
||||
ADD_FUNCTION(
|
||||
"*",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
bool return_decimal = false;
|
||||
|
||||
int64_t number = 1;
|
||||
double decimal = 1;
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
APPLY_NUMBER_OR_DECIMAL(it, {
|
||||
number *= it_numeric;
|
||||
decimal *= it_numeric;
|
||||
});
|
||||
}
|
||||
|
||||
RETURN_NUMBER_OR_DECIMAL();
|
||||
});
|
||||
|
||||
ADD_FUNCTION(
|
||||
"/",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_AT_LEAST("/", SIZE(), 1);
|
||||
|
||||
bool return_decimal = false;
|
||||
|
||||
int64_t number = 0;
|
||||
double decimal = 0;
|
||||
|
||||
// Start with the first number
|
||||
APPLY_NUMBER_OR_DECIMAL(begin, {
|
||||
number = it_numeric;
|
||||
decimal = it_numeric;
|
||||
});
|
||||
|
||||
// Skip the first node
|
||||
for (auto it = begin + 1; it != end; ++it) {
|
||||
APPLY_NUMBER_OR_DECIMAL(it, {
|
||||
number /= it_numeric;
|
||||
decimal /= it_numeric;
|
||||
});
|
||||
}
|
||||
|
||||
RETURN_NUMBER_OR_DECIMAL();
|
||||
});
|
||||
|
||||
// (% 5 2) -> 1
|
||||
ADD_FUNCTION(
|
||||
"%",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("/", SIZE(), 2);
|
||||
|
||||
VALUE_CAST(divide, Number, (*begin));
|
||||
VALUE_CAST(by, Number, (*(begin + 1)));
|
||||
|
||||
return makePtr<Number>(divide->number() % by->number());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <chrono> // std::chrono::sytem_clock
|
||||
#include <cstdint> // int64_t
|
||||
#include <filesystem> // std::filesystem::current_path
|
||||
|
||||
#include "ruc/file.h"
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadOther()
|
||||
{
|
||||
ADD_FUNCTION(
|
||||
"pwd", "",
|
||||
"Return the full filename of the current working directory.",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("pwd", SIZE(), 0);
|
||||
|
||||
auto path = std::filesystem::current_path().string();
|
||||
return makePtr<String>(path);
|
||||
});
|
||||
|
||||
ADD_FUNCTION(
|
||||
"slurp", "",
|
||||
"Read file contents",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("slurp", SIZE(), 1);
|
||||
|
||||
VALUE_CAST(node, String, (*begin));
|
||||
std::string path = node->data();
|
||||
|
||||
auto file = ruc::File(path);
|
||||
|
||||
return makePtr<String>(file.data());
|
||||
});
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// (throw x)
|
||||
ADD_FUNCTION(
|
||||
"throw", "",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("throw", SIZE(), 1);
|
||||
|
||||
Error::the().add(*begin);
|
||||
|
||||
return nullptr;
|
||||
})
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// (time-ms)
|
||||
ADD_FUNCTION(
|
||||
"time-ms", "",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("time-ms", SIZE(), 0);
|
||||
|
||||
int64_t elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
return makePtr<Number>(elapsed);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadPredicate()
|
||||
{
|
||||
#define IS_CONSTANT(name, constant) \
|
||||
{ \
|
||||
CHECK_ARG_COUNT_IS(name, SIZE(), 1); \
|
||||
\
|
||||
return makePtr<Constant>( \
|
||||
is<Constant>(begin->get()) \
|
||||
&& std::static_pointer_cast<Constant>(*begin)->state() == constant); \
|
||||
}
|
||||
|
||||
// (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));
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
#define IS_TYPE(type) \
|
||||
{ \
|
||||
bool result = true; \
|
||||
\
|
||||
if (SIZE() == 0) { \
|
||||
result = false; \
|
||||
} \
|
||||
\
|
||||
for (auto it = begin; it != end; ++it) { \
|
||||
if (!is<type>(it->get())) { \
|
||||
result = false; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
return makePtr<Constant>(result); \
|
||||
}
|
||||
|
||||
// (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(
|
||||
"fn?",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if (SIZE() == 0) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
if (!is<Callable>(it->get()) || is<Macro>(it->get())) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return makePtr<Constant>(result);
|
||||
});
|
||||
|
||||
ADD_FUNCTION(
|
||||
"macro?",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if (SIZE() == 0) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
if (!is<Macro>(it->get())) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return makePtr<Constant>(result);
|
||||
});
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// (contains? {:foo 5} :foo) -> true
|
||||
// (contains? {"bar" 5} "foo") -> false
|
||||
ADD_FUNCTION(
|
||||
"contains?",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("contains?", SIZE(), 2);
|
||||
|
||||
VALUE_CAST(hash_map, HashMap, (*begin));
|
||||
|
||||
if (SIZE() == 0) {
|
||||
return makePtr<Constant>(false);
|
||||
}
|
||||
|
||||
return makePtr<Constant>(hash_map->exists(*(begin + 1)));
|
||||
});
|
||||
|
||||
// (empty? '() '()) -> true
|
||||
// (empty? [] [1 2 3] []) -> false
|
||||
ADD_FUNCTION(
|
||||
"empty?",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
VALUE_CAST(collection, Collection, (*it));
|
||||
if (!collection->empty()) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return makePtr<Constant>((result) ? Constant::True : Constant::False);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/macro.h"
|
||||
#include "blaze/repl.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
void Environment::loadRepl()
|
||||
{
|
||||
// REPL reader
|
||||
ADD_FUNCTION(
|
||||
"read-string",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("read-string", SIZE(), 1);
|
||||
|
||||
VALUE_CAST(node, String, (*begin));
|
||||
std::string input = node->data();
|
||||
|
||||
return Repl::read(input);
|
||||
});
|
||||
|
||||
// Prompt readline
|
||||
ADD_FUNCTION(
|
||||
"readline",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("readline", SIZE(), 1);
|
||||
|
||||
VALUE_CAST(prompt, String, (*begin));
|
||||
|
||||
return Repl::readline(prompt->data());
|
||||
});
|
||||
|
||||
// REPL eval
|
||||
ADD_FUNCTION(
|
||||
"eval",
|
||||
"",
|
||||
"",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("eval", SIZE(), 1);
|
||||
|
||||
return Repl::eval(*begin, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iterator> // std::distance
|
||||
#include <unordered_map>
|
||||
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/forward.h"
|
||||
|
||||
#define ADD_FUNCTION(name, signature, documentation, lambda) \
|
||||
blaze::Environment::registerFunction( \
|
||||
{ name, \
|
||||
signature, \
|
||||
documentation, \
|
||||
[](blaze::ValueVectorConstIt begin, blaze::ValueVectorConstIt end) -> blaze::ValuePtr lambda });
|
||||
|
||||
#define SIZE() std::distance(begin, end)
|
||||
@@ -10,7 +10,8 @@
|
||||
|
||||
#include "ruc/singleton.h"
|
||||
|
||||
#include "lexer.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/lexer.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
@@ -23,23 +24,28 @@ public:
|
||||
{
|
||||
m_token_errors.clear();
|
||||
m_other_errors.clear();
|
||||
m_exceptions.clear();
|
||||
}
|
||||
void addError(Token error) { m_token_errors.push_back(error); }
|
||||
void addError(const std::string& error) { m_other_errors.push_back(error); }
|
||||
void add(Token error) { m_token_errors.push_back(error); }
|
||||
void add(const std::string& error) { m_other_errors.push_back(error); }
|
||||
void add(ValuePtr error) { m_exceptions.push_back(error); }
|
||||
|
||||
bool hasTokenError() { return m_token_errors.size() > 0; }
|
||||
bool hasOtherError() { return m_other_errors.size() > 0; }
|
||||
bool hasAnyError() { return hasTokenError() || hasOtherError(); }
|
||||
bool hasException() { return m_exceptions.size() > 0; }
|
||||
bool hasAnyError() { return hasTokenError() || hasOtherError() || hasException(); }
|
||||
|
||||
void setInput(std::string_view input) { m_input = input; }
|
||||
|
||||
Token tokenError() const { return m_token_errors[0]; }
|
||||
const std::string& otherError() const { return m_other_errors[0]; }
|
||||
ValuePtr exception() const { return m_exceptions[0]; }
|
||||
|
||||
private:
|
||||
std::string_view m_input;
|
||||
std::vector<Token> m_token_errors;
|
||||
std::vector<std::string> m_other_errors;
|
||||
std::vector<ValuePtr> m_exceptions;
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,666 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <algorithm> // std::find_if, std::transform
|
||||
#include <cctype> // std::toupper
|
||||
#include <iterator> // std::distance, std::next, std::prev
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "ruc/format/color.h"
|
||||
#include "ruc/format/format.h"
|
||||
#include "ruc/format/print.h"
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/eval.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/macro.h"
|
||||
#include "blaze/printer.h"
|
||||
#include "blaze/settings.h"
|
||||
#include "blaze/types.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
static ValuePtr evalQuasiQuoteImpl(ValuePtr ast);
|
||||
|
||||
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);
|
||||
|
||||
// First argument needs to be a Symbol
|
||||
VALUE_CAST(symbol, Symbol, nodes.front());
|
||||
|
||||
// Eval second argument
|
||||
m_ast = *std::next(nodes.begin());
|
||||
m_env = env;
|
||||
ValuePtr value = evalImpl();
|
||||
|
||||
// Dont overwrite symbols after an error
|
||||
if (Error::the().hasAnyError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Modify existing environment
|
||||
return env->set(symbol->symbol(), value);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// First argument needs to be a Symbol
|
||||
VALUE_CAST(symbol, Symbol, nodes.front());
|
||||
|
||||
// Eval second argument
|
||||
m_ast = *std::next(nodes.begin());
|
||||
m_env = env;
|
||||
ValuePtr value = evalImpl();
|
||||
VALUE_CAST(lambda, Lambda, value);
|
||||
|
||||
// Dont overwrite symbols after an error
|
||||
if (Error::the().hasAnyError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Modify existing environment
|
||||
return env->set(symbol->symbol(), makePtr<Macro>(*lambda));
|
||||
}
|
||||
|
||||
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().getEnvBool("*PRETTY-PRINT*");
|
||||
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<Callable>(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<Function>(value.get())) {
|
||||
type = "function";
|
||||
|
||||
auto function = std::static_pointer_cast<Function>(value);
|
||||
signature += !function->bindings().empty() ? " " : "";
|
||||
signature += function->bindings();
|
||||
|
||||
documentation = function->documentation();
|
||||
}
|
||||
else if (is<Lambda>(value.get()) || is<Macro>(value.get())) {
|
||||
type = is<Lambda>(value.get()) ? "function" : "macro";
|
||||
|
||||
auto lambda = std::static_pointer_cast<Lambda>(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<String>(body.get())) {
|
||||
documentation = std::static_pointer_cast<String>(body)->data();
|
||||
}
|
||||
else if (is<List>(body.get())) {
|
||||
VALUE_CAST(list, List, body);
|
||||
if (list->size() > 1) {
|
||||
auto second = list->nodesRead()[1];
|
||||
if (is<String>(second.get())) {
|
||||
documentation = std::static_pointer_cast<String>(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);
|
||||
|
||||
// First element needs to be a List or Vector
|
||||
VALUE_CAST(collection, Collection, nodes.front());
|
||||
const auto& collection_nodes = collection->nodesRead();
|
||||
|
||||
std::vector<std::string> bindings;
|
||||
bindings.reserve(collection_nodes.size());
|
||||
for (const auto& node : collection_nodes) {
|
||||
// All nodes need to be a Symbol
|
||||
VALUE_CAST(symbol, Symbol, node);
|
||||
bindings.push_back(symbol->symbol());
|
||||
}
|
||||
|
||||
// If more than one s-exp in lambda body, wrap in list
|
||||
if (nodes.size() > 2) {
|
||||
auto first = std::next(nodes.begin());
|
||||
auto last = std::prev(nodes.end());
|
||||
auto body_nodes = ValueVector(std::distance(first, last) + 2);
|
||||
body_nodes.at(0) = makePtr<Symbol>("do");
|
||||
std::copy(first, nodes.end(), body_nodes.begin() + 1);
|
||||
auto body = makePtr<List>(body_nodes);
|
||||
|
||||
return makePtr<Lambda>(bindings, body, env);
|
||||
}
|
||||
|
||||
return makePtr<Lambda>(bindings, *std::next(nodes.begin()), env);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// (quasiquoteexpand x)
|
||||
EVAL_FUNCTION("quasiquoteexpand", "arg", ""); // TODO
|
||||
ValuePtr Eval::evalQuasiQuoteExpand(const ValueVector& nodes)
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("quasiquoteexpand", nodes.size(), 1);
|
||||
|
||||
return evalQuasiQuoteImpl(nodes.front());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return nodes.front();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Is last node a catch* block
|
||||
bool is_last_node_catch = false;
|
||||
std::span<const ValuePtr> catch_nodes;
|
||||
if (nodes.size() >= 2 && is<List>(nodes.back().get())) {
|
||||
VALUE_CAST(list, List, nodes.back());
|
||||
catch_nodes = list->nodesRead();
|
||||
if (!list->empty() && is<Symbol>(catch_nodes.front().get())) {
|
||||
VALUE_CAST(catch_symbol, Symbol, catch_nodes.front());
|
||||
if (catch_symbol->symbol() == "catch*") {
|
||||
CHECK_ARG_COUNT_IS("catch*", catch_nodes.size() - 1, 2);
|
||||
is_last_node_catch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dont have to eval on malformed (catch*)
|
||||
if (Error::the().hasAnyError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Try
|
||||
|
||||
ValuePtr result;
|
||||
auto end = (!is_last_node_catch) ? nodes.end() : std::prev(nodes.end(), 1);
|
||||
for (auto it = nodes.begin(); it != end; ++it) {
|
||||
m_ast = *it;
|
||||
m_env = env;
|
||||
result = evalImpl();
|
||||
}
|
||||
if (!Error::the().hasAnyError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Catch
|
||||
|
||||
if (!is_last_node_catch) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the error message
|
||||
auto error = (Error::the().hasOtherError())
|
||||
? makePtr<String>(Error::the().otherError())
|
||||
: Error::the().exception();
|
||||
Error::the().clearErrors();
|
||||
|
||||
VALUE_CAST(catch_binding, Symbol, (*std::next(catch_nodes.begin())));
|
||||
|
||||
// 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 'handler' using the new Environment
|
||||
m_ast = catch_nodes.back();
|
||||
m_env = catch_env;
|
||||
return evalImpl();
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
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>(Constant::True);
|
||||
for (auto node : nodes) {
|
||||
m_ast = node;
|
||||
m_env = env;
|
||||
result = evalImpl();
|
||||
|
||||
if (is<Constant>(result.get())) {
|
||||
VALUE_CAST(constant, Constant, result, void());
|
||||
if (constant->state() == Constant::Nil || constant->state() == Constant::False) {
|
||||
m_ast = makePtr<Constant>(Constant::Nil);
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_ast = result;
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
// Evaluate all nodes except the last
|
||||
for (auto it = nodes.begin(); it != std::prev(nodes.end(), 1); ++it) {
|
||||
m_ast = *it;
|
||||
m_env = env;
|
||||
evalImpl();
|
||||
}
|
||||
|
||||
// Eval last node
|
||||
m_ast = nodes.back();
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
auto first_argument = *nodes.begin();
|
||||
auto second_argument = *std::next(nodes.begin());
|
||||
auto third_argument = (nodes.size() == 3) ? *std::next(std::next(nodes.begin())) : makePtr<Constant>(Constant::Nil);
|
||||
|
||||
m_ast = first_argument;
|
||||
m_env = env;
|
||||
auto first_evaluated = evalImpl();
|
||||
if (!is<Constant>(first_evaluated.get())
|
||||
|| std::static_pointer_cast<Constant>(first_evaluated)->state() == Constant::True) {
|
||||
m_ast = second_argument;
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
m_ast = third_argument;
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
// First argument needs to be a List or Vector
|
||||
VALUE_CAST(bindings, Collection, nodes.front(), void());
|
||||
const auto& binding_nodes = bindings->nodesRead();
|
||||
|
||||
// List or Vector needs to have an even number of elements
|
||||
CHECK_ARG_COUNT_EVEN("bindings", binding_nodes.size(), void());
|
||||
|
||||
// Create new environment
|
||||
auto let_env = Environment::create(env);
|
||||
|
||||
for (auto it = binding_nodes.begin(); it != binding_nodes.end(); std::advance(it, 2)) {
|
||||
// First element needs to be a Symbol
|
||||
VALUE_CAST(elt, Symbol, (*it), void());
|
||||
|
||||
std::string key = elt->symbol();
|
||||
m_ast = *std::next(it);
|
||||
m_env = let_env;
|
||||
ValuePtr value = evalImpl();
|
||||
let_env->set(key, value);
|
||||
}
|
||||
|
||||
// TODO: Remove limitation of 3 arguments
|
||||
// Eval all arguments in this new env, return last sexp of the result
|
||||
m_ast = *std::next(nodes.begin());
|
||||
m_env = let_env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// (x y z)
|
||||
static bool isMacroCall(ValuePtr ast, EnvironmentPtr env)
|
||||
{
|
||||
auto list = dynamic_cast<List*>(ast.get());
|
||||
|
||||
if (list == nullptr || list->empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto front = list->front().get();
|
||||
|
||||
if (!is<Symbol>(front)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto symbol = dynamic_cast<Symbol*>(front)->symbol();
|
||||
auto value = env->get(symbol).get();
|
||||
|
||||
if (!is<Macro>(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
if (!isMacroCall(nodes.front(), env)) {
|
||||
m_ast = nodes.front();
|
||||
m_env = env;
|
||||
return;
|
||||
}
|
||||
|
||||
auto list = std::static_pointer_cast<List>(nodes.front());
|
||||
|
||||
auto value = env->get(std::static_pointer_cast<Symbol>(list->front())->symbol());
|
||||
auto lambda = std::static_pointer_cast<Lambda>(value);
|
||||
|
||||
m_ast = lambda->body();
|
||||
m_env = Environment::create(lambda, list->rest());
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
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;
|
||||
for (auto node : nodes) {
|
||||
m_ast = node;
|
||||
m_env = env;
|
||||
result = evalImpl();
|
||||
|
||||
if (!is<Constant>(result.get())) {
|
||||
m_ast = result;
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
VALUE_CAST(constant, Constant, result, void());
|
||||
if (constant->state() == Constant::True) {
|
||||
m_ast = result;
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
}
|
||||
|
||||
m_ast = makePtr<Constant>(Constant::Nil);
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
static bool isSymbol(ValuePtr value, const std::string& symbol)
|
||||
{
|
||||
if (!is<Symbol>(value.get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto value_symbol = std::static_pointer_cast<Symbol>(value)->symbol();
|
||||
|
||||
if (value_symbol != symbol) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static ValuePtr startsWith(ValuePtr ast, const std::string& symbol)
|
||||
{
|
||||
if (!is<List>(ast.get())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& nodes = std::static_pointer_cast<List>(ast)->nodesRead();
|
||||
|
||||
if (nodes.empty() || !isSymbol(nodes.front(), symbol)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Dont count the Symbol as part of the arguments
|
||||
CHECK_ARG_COUNT_IS(symbol, nodes.size() - 1, 1);
|
||||
|
||||
return *std::next(nodes.begin());
|
||||
}
|
||||
|
||||
static ValuePtr evalQuasiQuoteImpl(ValuePtr ast)
|
||||
{
|
||||
if (is<HashMap>(ast.get()) || is<Symbol>(ast.get())) {
|
||||
return makePtr<List>(makePtr<Symbol>("quote"), ast);
|
||||
}
|
||||
|
||||
if (!is<Collection>(ast.get())) {
|
||||
return ast;
|
||||
}
|
||||
|
||||
// `~2 or `(unquote 2)
|
||||
const auto unquote = startsWith(ast, "unquote"); // x
|
||||
if (unquote) {
|
||||
return unquote;
|
||||
}
|
||||
|
||||
// `~@(list 2 2 2) or `(splice-unquote (list 2 2 2))
|
||||
const auto splice_unquote = startsWith(ast, "splice-unquote"); // (list 2 2 2)
|
||||
if (splice_unquote) {
|
||||
return splice_unquote;
|
||||
}
|
||||
|
||||
ValuePtr result = makePtr<List>();
|
||||
|
||||
auto collection = std::static_pointer_cast<Collection>(ast);
|
||||
|
||||
// `() or `(1 ~2 3) or `(1 ~@(list 2 2 2) 3)
|
||||
for (auto it = collection->beginReverse(); it != collection->endReverse(); ++it) {
|
||||
const auto& elt = *it;
|
||||
|
||||
const auto splice_unquote = startsWith(elt, "splice-unquote"); // (list 2 2 2)
|
||||
if (splice_unquote) {
|
||||
// (cons 1 (concat (list 2 2 2) (cons 3 ())))
|
||||
result = makePtr<List>(makePtr<Symbol>("concat"), splice_unquote, result);
|
||||
continue;
|
||||
}
|
||||
|
||||
// (cons 1 (cons 2 (cons 3 ())))
|
||||
result = makePtr<List>(makePtr<Symbol>("cons"), evalQuasiQuoteImpl(elt), result);
|
||||
}
|
||||
|
||||
if (is<List>(ast.get())) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wrap result in (vec) for Vector types
|
||||
return makePtr<List>(makePtr<Symbol>("vec"), result);
|
||||
}
|
||||
|
||||
// (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());
|
||||
|
||||
auto result = evalQuasiQuoteImpl(nodes.front());
|
||||
|
||||
m_ast = result;
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// (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());
|
||||
|
||||
// Condition
|
||||
ValuePtr predicate = *nodes.begin();
|
||||
|
||||
m_ast = predicate;
|
||||
m_env = env;
|
||||
ValuePtr condition = evalImpl();
|
||||
while (!is<Constant>(condition.get())
|
||||
|| std::static_pointer_cast<Constant>(condition)->state() == Constant::True) {
|
||||
for (auto it = nodes.begin() + 1; it != nodes.end(); ++it) {
|
||||
m_ast = *it;
|
||||
m_env = env;
|
||||
evalImpl();
|
||||
}
|
||||
|
||||
m_ast = predicate;
|
||||
m_env = env;
|
||||
condition = evalImpl();
|
||||
}
|
||||
|
||||
m_ast = makePtr<Constant>();
|
||||
m_env = env;
|
||||
return; // TCO
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <iterator> // std::advance, std::next, std::prev
|
||||
#include <list>
|
||||
#include <memory> // std::static_pointer_cast
|
||||
#include <span> // std::span
|
||||
#include <string>
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/eval.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/types.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
std::vector<SpecialFormParts> Eval::s_special_form_parts;
|
||||
|
||||
Eval::Eval(ValuePtr ast, EnvironmentPtr env)
|
||||
: m_ast(ast)
|
||||
, m_env(env)
|
||||
, m_outer_env(env)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Eval::registerSpecialForm(SpecialFormParts special_form_parts)
|
||||
{
|
||||
s_special_form_parts.push_back(special_form_parts);
|
||||
}
|
||||
|
||||
void Eval::eval()
|
||||
{
|
||||
m_ast = evalImpl();
|
||||
}
|
||||
|
||||
ValuePtr Eval::evalImpl()
|
||||
{
|
||||
ValuePtr ast = nullptr;
|
||||
EnvironmentPtr env = nullptr;
|
||||
|
||||
if (env == nullptr) {
|
||||
env = m_outer_env;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (Error::the().hasAnyError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ast = m_ast;
|
||||
env = m_env;
|
||||
|
||||
if (is<Symbol>(ast.get())) {
|
||||
return evalSymbol(ast, env);
|
||||
}
|
||||
if (is<Vector>(ast.get())) {
|
||||
return evalVector(ast, env);
|
||||
}
|
||||
if (is<HashMap>(ast.get())) {
|
||||
return evalHashMap(ast, env);
|
||||
}
|
||||
if (!is<List>(ast.get())) {
|
||||
return ast;
|
||||
}
|
||||
|
||||
auto list = std::static_pointer_cast<List>(ast);
|
||||
|
||||
if (list->empty()) {
|
||||
return ast;
|
||||
}
|
||||
|
||||
// Special forms
|
||||
if (is<Symbol>(list->front().get())) {
|
||||
auto symbol = std::static_pointer_cast<Symbol>(list->front())->symbol();
|
||||
const auto& nodes = list->rest();
|
||||
if (symbol == "def!") {
|
||||
return evalDef(nodes, env);
|
||||
}
|
||||
if (symbol == "defmacro!") {
|
||||
return evalDefMacro(nodes, env);
|
||||
}
|
||||
if (symbol == "describe") {
|
||||
return evalDescribe(nodes, env);
|
||||
}
|
||||
if (symbol == "fn*") {
|
||||
return evalFn(nodes, env);
|
||||
}
|
||||
if (symbol == "quasiquoteexpand") {
|
||||
return evalQuasiQuoteExpand(nodes);
|
||||
}
|
||||
if (symbol == "quote") {
|
||||
return evalQuote(nodes);
|
||||
}
|
||||
if (symbol == "try*") {
|
||||
return evalTry(nodes, env);
|
||||
}
|
||||
// Tail call optimized functions
|
||||
if (symbol == "and") {
|
||||
evalAnd(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
if (symbol == "do") {
|
||||
evalDo(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
if (symbol == "if") {
|
||||
evalIf(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
if (symbol == "let*") {
|
||||
evalLet(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
if (symbol == "macroexpand-1") {
|
||||
evalMacroExpand1(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
if (symbol == "or") {
|
||||
evalOr(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
if (symbol == "quasiquote") {
|
||||
evalQuasiQuote(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
if (symbol == "while") {
|
||||
evalWhile(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
}
|
||||
|
||||
m_ast = list->front();
|
||||
m_env = env;
|
||||
auto evaluated_front = evalImpl();
|
||||
auto unevaluated_nodes = list->rest();
|
||||
|
||||
if (is<Macro>(evaluated_front.get())) { // FIXME
|
||||
auto lambda = std::static_pointer_cast<Lambda>(evaluated_front);
|
||||
m_ast = lambda->body();
|
||||
m_env = Environment::create(lambda, std::move(unevaluated_nodes));
|
||||
m_ast = evalImpl();
|
||||
m_env = env;
|
||||
continue; // TCO
|
||||
}
|
||||
|
||||
ValueVector evaluated_nodes(unevaluated_nodes.size());
|
||||
for (size_t i = 0; i < unevaluated_nodes.size(); ++i) {
|
||||
m_ast = unevaluated_nodes[i];
|
||||
m_env = env;
|
||||
evaluated_nodes[i] = evalImpl();
|
||||
}
|
||||
|
||||
// Regular list
|
||||
if (is<Lambda>(evaluated_front.get())) {
|
||||
auto lambda = std::static_pointer_cast<Lambda>(evaluated_front);
|
||||
|
||||
m_ast = lambda->body();
|
||||
m_env = Environment::create(lambda, std::move(evaluated_nodes));
|
||||
continue; // TCO
|
||||
}
|
||||
|
||||
// Function call
|
||||
return apply(evaluated_front, evaluated_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
ValuePtr Eval::evalSymbol(ValuePtr ast, EnvironmentPtr env)
|
||||
{
|
||||
auto result = env->get(std::static_pointer_cast<Symbol>(ast)->symbol());
|
||||
if (!result) {
|
||||
Error::the().add(::format("'{}' not found", ast));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ValuePtr Eval::evalVector(ValuePtr ast, EnvironmentPtr env)
|
||||
{
|
||||
const auto& nodes = std::static_pointer_cast<Collection>(ast)->nodesRead();
|
||||
size_t count = nodes.size();
|
||||
auto evaluated_nodes = ValueVector(count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
m_ast = nodes[i];
|
||||
m_env = env;
|
||||
ValuePtr eval_node = evalImpl();
|
||||
if (eval_node == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
evaluated_nodes.at(i) = eval_node;
|
||||
}
|
||||
|
||||
return makePtr<Vector>(evaluated_nodes);
|
||||
}
|
||||
|
||||
ValuePtr Eval::evalHashMap(ValuePtr ast, EnvironmentPtr env)
|
||||
{
|
||||
const auto& elements = std::static_pointer_cast<HashMap>(ast)->elements();
|
||||
Elements evaluated_elements;
|
||||
for (const auto& element : elements) {
|
||||
m_ast = element.second;
|
||||
m_env = env;
|
||||
ValuePtr element_node = evalImpl();
|
||||
if (element_node == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
evaluated_elements.insert_or_assign(element.first, element_node);
|
||||
}
|
||||
|
||||
return makePtr<HashMap>(evaluated_elements);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
ValuePtr Eval::apply(ValuePtr function, const ValueVector& nodes)
|
||||
{
|
||||
if (!is<Function>(function.get())) {
|
||||
Error::the().add(::format("invalid function: {}", function));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto func = std::static_pointer_cast<Function>(function)->function();
|
||||
|
||||
return func(nodes.begin(), nodes.end());
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "blaze/forward.h" // EnvironmentPtr
|
||||
|
||||
namespace blaze {
|
||||
|
||||
// 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; }
|
||||
|
||||
private:
|
||||
ValuePtr evalImpl();
|
||||
ValuePtr evalSymbol(ValuePtr ast, EnvironmentPtr env);
|
||||
ValuePtr evalVector(ValuePtr ast, EnvironmentPtr env);
|
||||
ValuePtr evalHashMap(ValuePtr ast, EnvironmentPtr env);
|
||||
|
||||
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);
|
||||
ValuePtr evalTry(const ValueVector& nodes, EnvironmentPtr env);
|
||||
|
||||
void evalAnd(const ValueVector& nodes, EnvironmentPtr env);
|
||||
void evalDo(const ValueVector& nodes, EnvironmentPtr env);
|
||||
void evalIf(const ValueVector& nodes, EnvironmentPtr env);
|
||||
void evalLet(const ValueVector& nodes, EnvironmentPtr env);
|
||||
void evalMacroExpand1(const ValueVector& nodes, EnvironmentPtr env);
|
||||
void evalOr(const ValueVector& nodes, EnvironmentPtr env);
|
||||
void evalQuasiQuote(const ValueVector& nodes, EnvironmentPtr env);
|
||||
void evalWhile(const ValueVector& nodes, EnvironmentPtr env);
|
||||
|
||||
ValuePtr apply(ValuePtr function, const ValueVector& nodes);
|
||||
|
||||
ValuePtr m_ast;
|
||||
EnvironmentPtr m_env;
|
||||
EnvironmentPtr m_outer_env;
|
||||
|
||||
static std::vector<SpecialFormParts> s_special_form_parts;
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory> // std::shared_ptr
|
||||
#include <vector>
|
||||
|
||||
namespace blaze {
|
||||
|
||||
// -----------------------------------------
|
||||
// Types
|
||||
|
||||
class Value;
|
||||
class HashMap;
|
||||
typedef std::shared_ptr<Value> ValuePtr;
|
||||
typedef std::shared_ptr<HashMap> HashMapPtr;
|
||||
typedef std::vector<ValuePtr> ValueVector;
|
||||
typedef ValueVector::iterator ValueVectorIt;
|
||||
typedef ValueVector::reverse_iterator ValueVectorReverseIt;
|
||||
typedef ValueVector::const_iterator ValueVectorConstIt;
|
||||
typedef ValueVector::const_reverse_iterator ValueVectorConstReverseIt;
|
||||
|
||||
class Environment;
|
||||
typedef std::shared_ptr<Environment> EnvironmentPtr;
|
||||
|
||||
class Readline;
|
||||
|
||||
// -----------------------------------------
|
||||
// Functions
|
||||
|
||||
// -----------------------------------------
|
||||
// Variables
|
||||
|
||||
extern Readline g_readline;
|
||||
extern EnvironmentPtr g_outer_env;
|
||||
|
||||
} // namespace blaze
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string> // std::to_string
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ruc/format/print.h"
|
||||
#include "ruc/genericlexer.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "lexer.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/lexer.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
@@ -38,6 +38,12 @@ void Lexer::tokenize()
|
||||
case '~': // ~@ or ~
|
||||
consumeSpliceUnquoteOrUnquote();
|
||||
break;
|
||||
case '(':
|
||||
m_tokens.push_back({ Token::Type::ParenOpen, m_line, m_column, "(" });
|
||||
break;
|
||||
case ')':
|
||||
m_tokens.push_back({ Token::Type::ParenClose, m_line, m_column, ")" });
|
||||
break;
|
||||
case '[':
|
||||
m_tokens.push_back({ Token::Type::BracketOpen, m_line, m_column, "[" });
|
||||
break;
|
||||
@@ -50,12 +56,6 @@ void Lexer::tokenize()
|
||||
case '}':
|
||||
m_tokens.push_back({ Token::Type::BraceClose, m_line, m_column, "}" });
|
||||
break;
|
||||
case '(':
|
||||
m_tokens.push_back({ Token::Type::ParenOpen, m_line, m_column, "(" });
|
||||
break;
|
||||
case ')':
|
||||
m_tokens.push_back({ Token::Type::ParenClose, m_line, m_column, ")" });
|
||||
break;
|
||||
case '\'':
|
||||
m_tokens.push_back({ Token::Type::Quote, m_line, m_column, "'" });
|
||||
break;
|
||||
@@ -73,6 +73,11 @@ void Lexer::tokenize()
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
if (!consumeKeyword()) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case ';':
|
||||
consumeComment();
|
||||
break;
|
||||
@@ -119,22 +124,19 @@ bool Lexer::consumeSpliceUnquoteOrUnquote()
|
||||
bool Lexer::consumeString()
|
||||
{
|
||||
size_t column = m_column;
|
||||
std::string text = "\"";
|
||||
std::string text;
|
||||
|
||||
static std::unordered_set<char> exit = {
|
||||
'"',
|
||||
'\r',
|
||||
'\n',
|
||||
'\0',
|
||||
};
|
||||
|
||||
bool escape = false;
|
||||
char character = consume();
|
||||
for (;;) {
|
||||
char character = consume(); // "
|
||||
while (true) {
|
||||
character = peek();
|
||||
|
||||
if (!escape && character == '\\') {
|
||||
text += '\\';
|
||||
ignore();
|
||||
escape = true;
|
||||
continue;
|
||||
@@ -144,17 +146,19 @@ bool Lexer::consumeString()
|
||||
break;
|
||||
}
|
||||
|
||||
text += character;
|
||||
if (escape && character == 'n') {
|
||||
text += 0xa; // 10 or \n
|
||||
}
|
||||
else {
|
||||
text += character;
|
||||
}
|
||||
ignore();
|
||||
|
||||
escape = false;
|
||||
}
|
||||
|
||||
if (character == '"') {
|
||||
text += character;
|
||||
}
|
||||
else {
|
||||
Error::the().addError({ Token::Type::Error, m_line, column, "expected '\"', got EOF" });
|
||||
if (character != '"') {
|
||||
Error::the().add({ Token::Type::Error, m_line, column, "expected '\"', got EOF" });
|
||||
}
|
||||
|
||||
m_tokens.push_back({ Token::Type::String, m_line, column, text });
|
||||
@@ -162,46 +166,12 @@ bool Lexer::consumeString()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lexer::consumeComment()
|
||||
bool Lexer::consumeKeyword()
|
||||
{
|
||||
size_t column = m_column;
|
||||
std::string comment = "";
|
||||
std::string keyword;
|
||||
|
||||
ignore(); // ;
|
||||
|
||||
static std::unordered_set<char> exit = {
|
||||
'\r',
|
||||
'\n',
|
||||
'\0',
|
||||
};
|
||||
|
||||
char character = 0;
|
||||
for (;;) {
|
||||
character = peek();
|
||||
|
||||
if (exit.find(character) != exit.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
comment += character;
|
||||
ignore();
|
||||
}
|
||||
|
||||
// Trim comment
|
||||
comment.erase(comment.begin(),
|
||||
std::find_if(comment.begin(), comment.end(), [](char c) { return !std::isspace(c); }));
|
||||
comment.erase(std::find_if(comment.rbegin(), comment.rend(), [](char c) { return !std::isspace(c); }).base(),
|
||||
comment.end());
|
||||
|
||||
m_tokens.push_back({ Token::Type::Comment, m_line, column, comment });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lexer::consumeValue()
|
||||
{
|
||||
size_t column = m_column;
|
||||
std::string value = "";
|
||||
ignore(); // :
|
||||
|
||||
static std::unordered_set<char> exit = {
|
||||
'[',
|
||||
@@ -223,7 +193,50 @@ bool Lexer::consumeValue()
|
||||
};
|
||||
|
||||
char character = 0;
|
||||
for (;;) {
|
||||
while (true) {
|
||||
character = peek();
|
||||
|
||||
if (exit.find(character) != exit.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
keyword += character;
|
||||
ignore();
|
||||
}
|
||||
|
||||
m_tokens.push_back({ Token::Type::Keyword, m_line, column, keyword });
|
||||
|
||||
retreat();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lexer::consumeValue()
|
||||
{
|
||||
size_t column = m_column;
|
||||
std::string value;
|
||||
|
||||
static std::unordered_set<char> exit = {
|
||||
'[',
|
||||
']',
|
||||
'{',
|
||||
'}',
|
||||
'(',
|
||||
')',
|
||||
'\'',
|
||||
'`',
|
||||
',',
|
||||
'"',
|
||||
';',
|
||||
' ',
|
||||
'\t',
|
||||
'\r',
|
||||
'\n',
|
||||
'\0',
|
||||
};
|
||||
|
||||
char character = 0;
|
||||
while (true) {
|
||||
character = peek();
|
||||
|
||||
if (exit.find(character) != exit.end()) {
|
||||
@@ -241,6 +254,30 @@ bool Lexer::consumeValue()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lexer::consumeComment()
|
||||
{
|
||||
ignore(); // ;
|
||||
|
||||
static std::unordered_set<char> exit = {
|
||||
'\r',
|
||||
'\n',
|
||||
'\0',
|
||||
};
|
||||
|
||||
char character = 0;
|
||||
while (true) {
|
||||
character = peek();
|
||||
|
||||
if (exit.find(character) != exit.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
ignore();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lexer::dump() const
|
||||
{
|
||||
print("tokens: {}\n", m_tokens.size());
|
||||
@@ -20,20 +20,20 @@ struct Token {
|
||||
enum class Type : uint8_t {
|
||||
None,
|
||||
Special, // ~@
|
||||
ParenOpen, // (
|
||||
ParenClose, // )
|
||||
BracketOpen, // [
|
||||
BracketClose, // ]
|
||||
BraceOpen, // {
|
||||
BraceClose, // }
|
||||
ParenOpen, // (
|
||||
ParenClose, // )
|
||||
Quote, // '
|
||||
Backtick, // `
|
||||
Tilde, // ~
|
||||
Caret, // ^
|
||||
At, // @
|
||||
String, // "foobar"
|
||||
Comment, // ;
|
||||
Value, // symbols, numbers, "true", "false", and "nil"
|
||||
Keyword, // :keyword
|
||||
Value, // number, "nil", "true", "false", symbol
|
||||
Error,
|
||||
};
|
||||
|
||||
@@ -58,8 +58,9 @@ public:
|
||||
private:
|
||||
bool consumeSpliceUnquoteOrUnquote(); // ~@ or ~
|
||||
bool consumeString();
|
||||
bool consumeComment();
|
||||
bool consumeKeyword();
|
||||
bool consumeValue();
|
||||
bool consumeComment();
|
||||
|
||||
size_t m_column { 0 };
|
||||
size_t m_line { 0 };
|
||||
@@ -68,44 +69,3 @@ private:
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
// ~^@
|
||||
// (+ 2 (* 3 4))
|
||||
|
||||
// Lexing -> creates tokens
|
||||
// Parsing -> creates AST
|
||||
|
||||
// class Thing1 {
|
||||
// public:
|
||||
// std::vector<int>& numbers() { return m_numbers; }
|
||||
|
||||
// private:
|
||||
// std::vector<int> m_numbers;
|
||||
// };
|
||||
|
||||
// class Thing2 {
|
||||
// public:
|
||||
// std::vector<int>&& numbers() { return std::move(m_numbers); }
|
||||
|
||||
// private:
|
||||
// std::vector<int> m_numbers;
|
||||
// };
|
||||
|
||||
// class OtherThing {
|
||||
// public:
|
||||
// OtherThing(std::vector<int>&& numbers) noexcept
|
||||
// : m_numbers(std::move(numbers))
|
||||
// {
|
||||
// }
|
||||
|
||||
// private:
|
||||
// std::vector<int> m_numbers;
|
||||
// };
|
||||
|
||||
// int main()
|
||||
// {
|
||||
// Thing1 thing1;
|
||||
// Thing2 thing2;
|
||||
// OtherThing other_thing(std::move(thing1.numbers()));
|
||||
// OtherThing other_thing2(thing2.numbers());
|
||||
// }
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "blaze/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__))
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <iterator> // std::next
|
||||
#include <memory> // std::static_pointer_cast
|
||||
#include <string>
|
||||
|
||||
#include "ruc/format/color.h"
|
||||
#include "ruc/format/format.h"
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/lexer.h"
|
||||
#include "blaze/printer.h"
|
||||
#include "blaze/settings.h"
|
||||
#include "blaze/types.h"
|
||||
#include "blaze/util.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
Printer::Printer()
|
||||
{
|
||||
}
|
||||
|
||||
Printer::~Printer()
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
std::string Printer::print(ValuePtr value, bool print_readably)
|
||||
{
|
||||
if (Error::the().hasAnyError()) {
|
||||
init();
|
||||
printError();
|
||||
return m_print;
|
||||
}
|
||||
|
||||
return printNoErrorCheck(value, print_readably);
|
||||
}
|
||||
|
||||
std::string Printer::printNoErrorCheck(ValuePtr value, bool print_readably)
|
||||
{
|
||||
init();
|
||||
|
||||
if (value == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
printImpl(value, print_readably);
|
||||
|
||||
return m_print;
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Printer::init()
|
||||
{
|
||||
m_first_node = true;
|
||||
m_previous_node_is_list = false;
|
||||
m_print = "";
|
||||
}
|
||||
|
||||
void Printer::printImpl(ValuePtr value, bool print_readably)
|
||||
{
|
||||
bool pretty_print = Settings::the().getEnvBool("*PRETTY-PRINT*");
|
||||
|
||||
auto printSpacing = [this]() -> void {
|
||||
if (!m_first_node && !m_previous_node_is_list) {
|
||||
m_print += ' ';
|
||||
}
|
||||
};
|
||||
|
||||
Value* value_raw_ptr = value.get();
|
||||
if (is<Collection>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += (is<List>(value_raw_ptr)) ? '(' : '[';
|
||||
m_first_node = false;
|
||||
m_previous_node_is_list = true;
|
||||
auto nodes = std::static_pointer_cast<Collection>(value)->nodesRead();
|
||||
for (auto node : nodes) {
|
||||
printImpl(node, print_readably);
|
||||
m_previous_node_is_list = false;
|
||||
}
|
||||
m_print += (is<List>(value_raw_ptr)) ? ')' : ']';
|
||||
}
|
||||
else if (is<HashMap>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += "{";
|
||||
m_first_node = false;
|
||||
m_previous_node_is_list = true;
|
||||
auto elements = std::static_pointer_cast<HashMap>(value)->elements();
|
||||
for (auto it = elements.begin(); it != elements.end(); ++it) {
|
||||
m_print += ::format("{} ", (it->first.front() == 0x7f) ? ":" + it->first.substr(1) : '"' + it->first + '"'); // 127
|
||||
printImpl(it->second, print_readably);
|
||||
|
||||
if (!isLast(it, elements)) {
|
||||
m_print += ' ';
|
||||
}
|
||||
}
|
||||
m_previous_node_is_list = false;
|
||||
m_print += '}';
|
||||
}
|
||||
else if (is<String>(value_raw_ptr)) {
|
||||
std::string text = std::static_pointer_cast<String>(value)->data();
|
||||
if (print_readably) {
|
||||
text = replaceAll(text, "\\", "\\\\");
|
||||
text = replaceAll(text, "\"", "\\\"");
|
||||
text = replaceAll(text, "\n", "\\n");
|
||||
text = "\"" + text + "\"";
|
||||
}
|
||||
printSpacing();
|
||||
if (pretty_print) {
|
||||
m_print += ::format(fg(ruc::format::TerminalColor::BrightGreen), "{}", text);
|
||||
}
|
||||
else {
|
||||
m_print += ::format("{}", text);
|
||||
}
|
||||
}
|
||||
else if (is<Keyword>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += ::format(":{}", std::static_pointer_cast<Keyword>(value)->keyword().substr(1));
|
||||
}
|
||||
else if (is<Number>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += ::format("{}", std::static_pointer_cast<Number>(value)->number());
|
||||
}
|
||||
else if (is<Decimal>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += ::format("{:.15}", std::static_pointer_cast<Decimal>(value)->decimal());
|
||||
}
|
||||
else if (is<Constant>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
std::string constant;
|
||||
switch (std::static_pointer_cast<Constant>(value)->state()) {
|
||||
case Constant::Nil: constant = "nil"; break;
|
||||
case Constant::True: constant = "true"; break;
|
||||
case Constant::False: constant = "false"; break;
|
||||
}
|
||||
m_print += ::format("{}", constant);
|
||||
}
|
||||
else if (is<Symbol>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += ::format("{}", std::static_pointer_cast<Symbol>(value)->symbol());
|
||||
}
|
||||
else if (is<Function>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += ::format("#<builtin-function>({})", std::static_pointer_cast<Function>(value)->name());
|
||||
}
|
||||
else if (is<Lambda>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += ::format("#<user-function>({:p})", value_raw_ptr);
|
||||
}
|
||||
else if (is<Macro>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += ::format("#<user-macro>({:p})", value_raw_ptr);
|
||||
}
|
||||
else if (is<Atom>(value_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += "(atom ";
|
||||
printImpl(std::static_pointer_cast<Atom>(value)->deref(), print_readably);
|
||||
m_print += ")";
|
||||
}
|
||||
}
|
||||
|
||||
void Printer::printError()
|
||||
{
|
||||
m_print = "Error: ";
|
||||
if (Error::the().hasTokenError()) {
|
||||
Token error = Error::the().tokenError();
|
||||
m_print += ::format("{}", error.symbol);
|
||||
}
|
||||
else if (Error::the().hasOtherError()) {
|
||||
std::string error = Error::the().otherError();
|
||||
m_print += ::format("{}", error);
|
||||
}
|
||||
else if (Error::the().hasException()) {
|
||||
ValuePtr error = Error::the().exception();
|
||||
m_print += ::format("{}", error);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "blaze/ast.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
// Serializer -> return to string
|
||||
class Printer {
|
||||
public:
|
||||
Printer();
|
||||
virtual ~Printer();
|
||||
|
||||
std::string print(ValuePtr value, bool print_readably = true);
|
||||
std::string printNoErrorCheck(ValuePtr value, bool print_readably = true);
|
||||
|
||||
private:
|
||||
void init();
|
||||
void printImpl(ValuePtr value, bool print_readably = true);
|
||||
void printError();
|
||||
|
||||
bool m_first_node { true };
|
||||
bool m_previous_node_is_list { false };
|
||||
std::string m_print;
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <charconv> // std::from_chars
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // std::strtoll
|
||||
#include <memory> // std::static_pointer_cast
|
||||
#include <system_error> // std::errc
|
||||
#include <utility> // std::move
|
||||
|
||||
#include "ruc/format/color.h"
|
||||
#include "ruc/meta/assert.h"
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/reader.h"
|
||||
#include "blaze/settings.h"
|
||||
#include "blaze/types.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
Reader::Reader()
|
||||
{
|
||||
}
|
||||
|
||||
Reader::Reader(std::vector<Token>&& tokens) noexcept
|
||||
: m_tokens(std::move(tokens))
|
||||
{
|
||||
}
|
||||
|
||||
Reader::~Reader()
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Reader::read()
|
||||
{
|
||||
if (Error::the().hasAnyError() || m_node) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_node = readImpl();
|
||||
|
||||
if (Error::the().hasOtherError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for multiple expressions
|
||||
if (!isEOF()) {
|
||||
Error::the().add("more than one sexp in input");
|
||||
}
|
||||
}
|
||||
|
||||
ValuePtr Reader::readImpl()
|
||||
{
|
||||
if (m_tokens.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
switch (peek().type) {
|
||||
case Token::Type::Special: // ~@
|
||||
return readSpliceUnquote();
|
||||
break;
|
||||
case Token::Type::ParenOpen: // (
|
||||
return readList();
|
||||
break;
|
||||
case Token::Type::ParenClose: // )
|
||||
Error::the().add("invalid read syntax: ')'");
|
||||
return nullptr;
|
||||
break;
|
||||
case Token::Type::BracketOpen: // [
|
||||
return readVector();
|
||||
break;
|
||||
case Token::Type::BracketClose: // ]
|
||||
Error::the().add("invalid read syntax: ']'");
|
||||
return nullptr;
|
||||
break;
|
||||
case Token::Type::BraceOpen: // {
|
||||
return readHashMap();
|
||||
break;
|
||||
case Token::Type::BraceClose: // }
|
||||
Error::the().add("invalid read syntax: '}'");
|
||||
return nullptr;
|
||||
break;
|
||||
case Token::Type::Quote: // '
|
||||
return readQuote();
|
||||
break;
|
||||
case Token::Type::Backtick: // `
|
||||
return readQuasiQuote();
|
||||
break;
|
||||
case Token::Type::Tilde: // ~
|
||||
return readUnquote();
|
||||
break;
|
||||
case Token::Type::Caret: // ^
|
||||
return readWithMeta();
|
||||
break;
|
||||
case Token::Type::At: // @
|
||||
return readDeref();
|
||||
break;
|
||||
case Token::Type::String: // "foobar"
|
||||
return readString();
|
||||
break;
|
||||
case Token::Type::Keyword: // :keyword
|
||||
return readKeyword();
|
||||
break;
|
||||
case Token::Type::Value: // true, false, nil
|
||||
return readValue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
// Unimplemented token
|
||||
VERIFY_NOT_REACHED();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ValuePtr Reader::readSpliceUnquote()
|
||||
{
|
||||
ignore(); // ~@
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().add("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto nodes = ValueVector(2);
|
||||
nodes.at(0) = makePtr<Symbol>("splice-unquote");
|
||||
nodes.at(1) = readImpl();
|
||||
|
||||
return makePtr<List>(nodes);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readList()
|
||||
{
|
||||
ignore(); // (
|
||||
|
||||
auto nodes = ValueVector();
|
||||
while (!isEOF() && peek().type != Token::Type::ParenClose) {
|
||||
auto node = readImpl();
|
||||
if (node == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
nodes.push_back(node);
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::ParenClose, .symbol = "" })) { // )
|
||||
Error::the().add("expected ')', got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return makePtr<List>(nodes);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readVector()
|
||||
{
|
||||
ignore(); // [
|
||||
|
||||
auto nodes = ValueVector();
|
||||
while (!isEOF() && peek().type != Token::Type::BracketClose) {
|
||||
auto node = readImpl();
|
||||
if (node == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
nodes.push_back(node);
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::BracketClose, .symbol = "" })) { // ]
|
||||
Error::the().add("expected ']', got EOF");
|
||||
}
|
||||
|
||||
return makePtr<Vector>(nodes);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readHashMap()
|
||||
{
|
||||
ignore(); // {
|
||||
|
||||
Elements elements;
|
||||
while (!isEOF() && peek().type != Token::Type::BraceClose) {
|
||||
auto key = readImpl();
|
||||
|
||||
if (key == nullptr || isEOF()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (peek().type == Token::Type::BraceClose) {
|
||||
Error::the().add("hash-map requires an even-sized list");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!is<String>(key.get()) && !is<Keyword>(key.get())) {
|
||||
Error::the().add(::format("wrong argument type: string or keyword, {}", key));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto value = readImpl();
|
||||
elements.insert_or_assign(HashMap::getKeyString(key), value);
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::BraceClose, .symbol = "" })) { // }
|
||||
Error::the().add("expected '}', got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return makePtr<HashMap>(elements);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readQuote()
|
||||
{
|
||||
ignore(); // '
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().add("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto nodes = ValueVector(2);
|
||||
nodes.at(0) = makePtr<Symbol>("quote");
|
||||
nodes.at(1) = readImpl();
|
||||
|
||||
return makePtr<List>(nodes);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readQuasiQuote()
|
||||
{
|
||||
ignore(); // `
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().add("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto nodes = ValueVector(2);
|
||||
nodes.at(0) = makePtr<Symbol>("quasiquote");
|
||||
nodes.at(1) = readImpl();
|
||||
|
||||
return makePtr<List>(nodes);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readUnquote()
|
||||
{
|
||||
ignore(); // ~
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().add("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto nodes = ValueVector(2);
|
||||
nodes.at(0) = makePtr<Symbol>("unquote");
|
||||
nodes.at(1) = readImpl();
|
||||
|
||||
return makePtr<List>(nodes);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readWithMeta()
|
||||
{
|
||||
ignore(); // ^
|
||||
|
||||
ignore(); // first token
|
||||
if (isEOF()) { // second token
|
||||
Error::the().add("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
retreat();
|
||||
|
||||
auto nodes = ValueVector(3);
|
||||
nodes.at(0) = makePtr<Symbol>("with-meta");
|
||||
nodes.at(2) = readImpl(); // Note: second Value is read first
|
||||
nodes.at(1) = readImpl();
|
||||
|
||||
return makePtr<List>(nodes);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readDeref()
|
||||
{
|
||||
ignore(); // @
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().add("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto nodes = ValueVector(2);
|
||||
nodes.at(0) = makePtr<Symbol>("deref");
|
||||
nodes.at(1) = readImpl();
|
||||
|
||||
return makePtr<List>(nodes);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readString()
|
||||
{
|
||||
std::string symbol = consume().symbol;
|
||||
|
||||
return makePtr<String>(symbol);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readKeyword()
|
||||
{
|
||||
return makePtr<Keyword>(consume().symbol);
|
||||
}
|
||||
|
||||
ValuePtr Reader::readValue()
|
||||
{
|
||||
auto symbol = consume().symbol;
|
||||
|
||||
int64_t number;
|
||||
auto [_, error] = std::from_chars(symbol.data(), symbol.data() + symbol.size(), number);
|
||||
if (error == std::errc() && symbol.find('.') == std::string::npos) {
|
||||
return makePtr<Number>(number);
|
||||
}
|
||||
|
||||
double decimal;
|
||||
{
|
||||
auto [_, error] = std::from_chars(symbol.data(), symbol.data() + symbol.size(), decimal);
|
||||
if (error == std::errc()) {
|
||||
return makePtr<Decimal>(decimal);
|
||||
}
|
||||
}
|
||||
|
||||
if (symbol == "nil") {
|
||||
return makePtr<Constant>(Constant::Nil);
|
||||
}
|
||||
else if (symbol == "true") {
|
||||
return makePtr<Constant>(Constant::True);
|
||||
}
|
||||
else if (symbol == "false") {
|
||||
return makePtr<Constant>(Constant::False);
|
||||
}
|
||||
|
||||
return makePtr<Symbol>(symbol);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
bool Reader::isEOF() const
|
||||
{
|
||||
return m_index >= m_tokens.size();
|
||||
}
|
||||
|
||||
Token Reader::peek() const
|
||||
{
|
||||
VERIFY(!isEOF());
|
||||
return m_tokens[m_index];
|
||||
}
|
||||
|
||||
Token Reader::consume()
|
||||
{
|
||||
VERIFY(!isEOF());
|
||||
return m_tokens[m_index++];
|
||||
}
|
||||
|
||||
bool Reader::consumeSpecific(Token token)
|
||||
{
|
||||
if (isEOF() || peek().type != token.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ignore();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reader::ignore()
|
||||
{
|
||||
m_index++;
|
||||
}
|
||||
|
||||
void Reader::retreat()
|
||||
{
|
||||
m_index--;
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Reader::dump(ValuePtr node)
|
||||
{
|
||||
m_indentation = 0;
|
||||
dumpImpl((node != nullptr) ? node : m_node);
|
||||
}
|
||||
|
||||
void Reader::dumpImpl(ValuePtr node)
|
||||
{
|
||||
std::string indentation = std::string(m_indentation * INDENTATION_WIDTH, ' ');
|
||||
print("{}", indentation);
|
||||
|
||||
bool pretty_print = Settings::the().getEnvBool("*PRETTY-PRINT*");
|
||||
auto blue = fg(ruc::format::TerminalColor::BrightBlue);
|
||||
auto yellow = fg(ruc::format::TerminalColor::Yellow);
|
||||
|
||||
Value* node_raw_ptr = node.get();
|
||||
if (is<Collection>(node_raw_ptr)) {
|
||||
auto container = (is<List>(node_raw_ptr)) ? "List" : "Vector";
|
||||
auto parens = (is<List>(node_raw_ptr)) ? "()" : "[]";
|
||||
pretty_print ? print(blue, "{}", container) : print("{}", container);
|
||||
print(" <");
|
||||
pretty_print ? print(blue, "{}", parens) : print("{}", parens);
|
||||
print(">\n");
|
||||
m_indentation++;
|
||||
auto nodes = std::static_pointer_cast<List>(node)->nodesRead();
|
||||
for (auto node : nodes) {
|
||||
dumpImpl(node);
|
||||
}
|
||||
m_indentation--;
|
||||
return;
|
||||
}
|
||||
else if (is<HashMap>(node_raw_ptr)) {
|
||||
auto hash_map = std::static_pointer_cast<HashMap>(node);
|
||||
auto elements = hash_map->elements();
|
||||
pretty_print ? print(blue, "HashMap") : print("HashMap");
|
||||
print(" <");
|
||||
pretty_print ? print(blue, "{{}}") : print("{{}}");
|
||||
print(">\n");
|
||||
m_indentation++;
|
||||
ValuePtr key_node = nullptr;
|
||||
for (auto element : elements) {
|
||||
bool is_keyword = element.first.front() == 0x7f; // 127
|
||||
is_keyword
|
||||
? dumpImpl(makePtr<Keyword>(element.first.substr(1)))
|
||||
: dumpImpl(makePtr<String>(element.first));
|
||||
m_indentation++;
|
||||
dumpImpl(element.second);
|
||||
m_indentation--;
|
||||
}
|
||||
m_indentation--;
|
||||
return;
|
||||
}
|
||||
else if (is<String>(node_raw_ptr)) {
|
||||
pretty_print ? print(yellow, "StringNode") : print("StringNode");
|
||||
print(" <{}>", node);
|
||||
}
|
||||
else if (is<Keyword>(node_raw_ptr)) {
|
||||
pretty_print ? print(yellow, "KeywordNode") : print("KeywordNode");
|
||||
print(" <{}>", node);
|
||||
}
|
||||
else if (is<Number>(node_raw_ptr)) {
|
||||
pretty_print ? print(yellow, "NumberNode") : print("NumberNode");
|
||||
print(" <{}>", node);
|
||||
}
|
||||
else if (is<Decimal>(node_raw_ptr)) {
|
||||
pretty_print ? print(yellow, "DecimalNode") : print("DecimalNode");
|
||||
print(" <{}>", node);
|
||||
}
|
||||
else if (is<Constant>(node_raw_ptr)) {
|
||||
pretty_print ? print(yellow, "ValueNode") : print("ValueNode");
|
||||
print(" <{}>", node);
|
||||
}
|
||||
else if (is<Symbol>(node_raw_ptr)) {
|
||||
pretty_print ? print(yellow, "SymbolNode") : print("SymbolNode");
|
||||
print(" <{}>", node);
|
||||
}
|
||||
else if (is<Function>(node_raw_ptr)) {
|
||||
auto function = std::static_pointer_cast<Function>(node);
|
||||
pretty_print ? print(blue, "Function") : print("Function");
|
||||
print(" <");
|
||||
pretty_print ? print(blue, "{}", function->name()) : print("{}", function->name());
|
||||
print(">\n");
|
||||
|
||||
m_indentation++;
|
||||
indentation = std::string(m_indentation * INDENTATION_WIDTH, ' ');
|
||||
|
||||
// bindings
|
||||
print("{}", indentation);
|
||||
pretty_print ? print(blue, "Bindings") : print("Bindings");
|
||||
print(" <{}>\n", function->bindings());
|
||||
|
||||
m_indentation--;
|
||||
return;
|
||||
}
|
||||
else if (is<Lambda>(node_raw_ptr) || is<Macro>(node_raw_ptr)) {
|
||||
auto container = (is<Lambda>(node_raw_ptr)) ? "Lambda" : "Macro";
|
||||
auto lambda = std::static_pointer_cast<Lambda>(node);
|
||||
pretty_print ? print(blue, "{}", container) : print("{}", container);
|
||||
print(" <");
|
||||
pretty_print ? print(blue, "{:p}", node_raw_ptr) : print("{:p}", node_raw_ptr);
|
||||
print(">\n");
|
||||
|
||||
m_indentation++;
|
||||
indentation = std::string(m_indentation * INDENTATION_WIDTH, ' ');
|
||||
|
||||
// bindings
|
||||
print("{}", indentation);
|
||||
pretty_print ? print(blue, "Bindings") : print("Bindings");
|
||||
print(" <");
|
||||
const auto& bindings = lambda->bindings();
|
||||
for (size_t i = 0; i < bindings.size(); ++i) {
|
||||
print("{}{}", (i > 0) ? " " : "", bindings[i]);
|
||||
}
|
||||
print(">\n");
|
||||
|
||||
// body
|
||||
dumpImpl(lambda->body());
|
||||
|
||||
m_indentation--;
|
||||
return;
|
||||
}
|
||||
else if (is<Atom>(node_raw_ptr)) {
|
||||
pretty_print ? print(yellow, "AtomNode") : print("AtomNode");
|
||||
print(" <{}>", std::static_pointer_cast<Atom>(node)->deref());
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <memory> // std::shared_ptr
|
||||
#include <vector>
|
||||
|
||||
#include "blaze/ast.h"
|
||||
#include "blaze/lexer.h"
|
||||
|
||||
#define INDENTATION_WIDTH 2
|
||||
|
||||
namespace blaze {
|
||||
|
||||
// Parsing -> creates AST
|
||||
class Reader {
|
||||
public:
|
||||
Reader();
|
||||
Reader(std::vector<Token>&& tokens) noexcept;
|
||||
virtual ~Reader();
|
||||
|
||||
void read();
|
||||
|
||||
void dump(ValuePtr node = nullptr);
|
||||
|
||||
ValuePtr node() { return m_node; }
|
||||
|
||||
private:
|
||||
bool isEOF() const;
|
||||
Token peek() const;
|
||||
Token consume();
|
||||
bool consumeSpecific(Token token);
|
||||
void ignore();
|
||||
void retreat();
|
||||
|
||||
ValuePtr readImpl();
|
||||
ValuePtr readSpliceUnquote(); // ~@
|
||||
ValuePtr readList(); // ()
|
||||
ValuePtr readVector(); // []
|
||||
ValuePtr readHashMap(); // {}
|
||||
ValuePtr readQuote(); // '
|
||||
ValuePtr readQuasiQuote(); // `
|
||||
ValuePtr readUnquote(); // ~
|
||||
ValuePtr readWithMeta(); // ^
|
||||
ValuePtr readDeref(); // @
|
||||
ValuePtr readString(); // "foobar"
|
||||
ValuePtr readKeyword(); // :keyword
|
||||
ValuePtr readValue(); // number, "nil", "true", "false", symbol
|
||||
|
||||
void dumpImpl(ValuePtr node);
|
||||
|
||||
size_t m_index { 0 };
|
||||
size_t m_indentation { 0 };
|
||||
std::vector<Token> m_tokens;
|
||||
|
||||
char m_error_character { 0 };
|
||||
bool m_invalid_syntax { false };
|
||||
bool m_is_unbalanced { false };
|
||||
|
||||
ValuePtr m_node { nullptr };
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdlib> // std::free
|
||||
#include <iostream> // FILE
|
||||
#include <readline/history.h>
|
||||
#include <readline/readline.h>
|
||||
#include <readline/tilde.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "ruc/format/color.h"
|
||||
#include "ruc/format/print.h"
|
||||
|
||||
#include "blaze/readline.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
Readline::Readline(bool pretty_print, std::string_view history_path)
|
||||
: m_pretty_print(pretty_print)
|
||||
, m_history_path(tilde_expand(history_path.data()))
|
||||
{
|
||||
m_prompt = createPrompt("user> ");
|
||||
|
||||
read_history(m_history_path);
|
||||
}
|
||||
|
||||
Readline::~Readline()
|
||||
{
|
||||
std::free(m_history_path);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
std::string Readline::createPrompt(const std::string& prompt)
|
||||
{
|
||||
if (!m_pretty_print) {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
return format(fg(ruc::format::TerminalColor::Blue), "{}", prompt)
|
||||
+ format("\033[1m");
|
||||
}
|
||||
|
||||
bool Readline::get(std::string& output, const std::string& prompt)
|
||||
{
|
||||
char* line = readline(prompt.c_str());
|
||||
|
||||
if (m_pretty_print) {
|
||||
print("\033[0m");
|
||||
}
|
||||
|
||||
if (line == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add input to in-memory history
|
||||
add_history(line);
|
||||
append_history(1, m_history_path);
|
||||
|
||||
output = line;
|
||||
std::free(line);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Readline::get(std::string& output)
|
||||
{
|
||||
return get(output, m_prompt);
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "ruc/singleton.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
class Readline {
|
||||
public:
|
||||
Readline() = default;
|
||||
Readline(bool pretty_print, std::string_view history_path);
|
||||
virtual ~Readline();
|
||||
|
||||
std::string createPrompt(const std::string& prompt);
|
||||
|
||||
bool get(std::string& output, const std::string& prompt);
|
||||
bool get(std::string& output);
|
||||
|
||||
private:
|
||||
bool m_pretty_print { false };
|
||||
char* m_history_path;
|
||||
std::string m_prompt;
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdlib> // std::exit
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "ruc/format/print.h"
|
||||
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/eval.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/lexer.h"
|
||||
#include "blaze/printer.h"
|
||||
#include "blaze/reader.h"
|
||||
#include "blaze/readline.h"
|
||||
#include "blaze/repl.h"
|
||||
#include "blaze/settings.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
Readline g_readline;
|
||||
EnvironmentPtr g_outer_env;
|
||||
|
||||
auto Repl::init() -> void
|
||||
{
|
||||
g_outer_env = Environment::create();
|
||||
Environment::loadFunctions();
|
||||
Environment::installFunctions(g_outer_env);
|
||||
}
|
||||
|
||||
auto Repl::cleanup() -> void
|
||||
{
|
||||
g_outer_env = nullptr;
|
||||
}
|
||||
|
||||
auto Repl::readline(const std::string& prompt) -> ValuePtr
|
||||
{
|
||||
std::string input;
|
||||
if (g_readline.get(input, g_readline.createPrompt(prompt))) {
|
||||
return makePtr<String>(input);
|
||||
}
|
||||
|
||||
return makePtr<Constant>();
|
||||
}
|
||||
|
||||
auto Repl::read(std::string_view input) -> ValuePtr
|
||||
{
|
||||
Lexer lexer(input);
|
||||
lexer.tokenize();
|
||||
if (Settings::the().getEnvBool("*DUMP-LEXER*")) {
|
||||
lexer.dump();
|
||||
}
|
||||
|
||||
Reader reader(std::move(lexer.tokens()));
|
||||
reader.read();
|
||||
if (Settings::the().getEnvBool("*DUMP-READER*")) {
|
||||
reader.dump();
|
||||
}
|
||||
|
||||
return reader.node();
|
||||
}
|
||||
|
||||
auto Repl::eval(ValuePtr ast, EnvironmentPtr env) -> ValuePtr
|
||||
{
|
||||
if (env == nullptr) {
|
||||
env = g_outer_env;
|
||||
}
|
||||
|
||||
Eval eval(ast, env);
|
||||
eval.eval();
|
||||
|
||||
return eval.ast();
|
||||
}
|
||||
|
||||
auto Repl::print(ValuePtr value) -> std::string
|
||||
{
|
||||
Printer printer;
|
||||
|
||||
return printer.print(value, true);
|
||||
}
|
||||
|
||||
auto Repl::rep(std::string_view input, EnvironmentPtr env) -> std::string
|
||||
{
|
||||
Error::the().clearErrors();
|
||||
Error::the().setInput(input);
|
||||
|
||||
return print(eval(read(input), env));
|
||||
}
|
||||
|
||||
auto Repl::makeArgv(EnvironmentPtr env, std::vector<std::string> arguments) -> void
|
||||
{
|
||||
size_t count = arguments.size();
|
||||
auto nodes = ValueVector();
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
nodes.push_back(makePtr<String>(arguments[i]));
|
||||
}
|
||||
env->set("*ARGV*", makePtr<List>(nodes));
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/readline.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
class Repl {
|
||||
public:
|
||||
static auto init() -> void;
|
||||
static auto cleanup() -> void;
|
||||
|
||||
static auto eval(ValuePtr ast, EnvironmentPtr env) -> ValuePtr;
|
||||
static auto makeArgv(EnvironmentPtr env, std::vector<std::string> arguments) -> void;
|
||||
static auto print(ValuePtr value) -> std::string;
|
||||
static auto read(std::string_view input) -> ValuePtr;
|
||||
static auto readline(const std::string& prompt) -> ValuePtr;
|
||||
static auto rep(std::string_view input, EnvironmentPtr env) -> std::string;
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <memory> // std::static_pointer_cast
|
||||
|
||||
#include "ruc/meta/assert.h"
|
||||
|
||||
#include "blaze/env/environment.h"
|
||||
#include "blaze/forward.h"
|
||||
#include "blaze/settings.h"
|
||||
#include "blaze/types.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
std::string_view Settings::get(std::string_view key) const
|
||||
{
|
||||
VERIFY(m_settings.find(key) != m_settings.end(), "setting does not exist");
|
||||
return m_settings.at(key);
|
||||
};
|
||||
|
||||
bool Settings::getEnvBool(std::string_view key) const
|
||||
{
|
||||
auto env = g_outer_env->get(key);
|
||||
return is<Constant>(env.get()) && std::static_pointer_cast<Constant>(env)->state() == Constant::State::True;
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "ruc/singleton.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
class Settings final : public ruc::Singleton<Settings> {
|
||||
public:
|
||||
Settings(s) {}
|
||||
virtual ~Settings() = default;
|
||||
|
||||
std::string_view get(std::string_view key) const;
|
||||
void set(std::string_view key, std::string_view value) { m_settings[key] = value; };
|
||||
|
||||
bool getEnvBool(std::string_view key) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string_view, std::string_view> m_settings;
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility> // std::forward
|
||||
|
||||
#include "ruc/meta/odr.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// struct hashMapConstructor {
|
||||
// template<typename HashMap>
|
||||
// static void construct(HashMap& hash_map, bool boolean)
|
||||
// {
|
||||
// //..
|
||||
// }
|
||||
// };
|
||||
|
||||
// template<typename HashMap, typename T>
|
||||
// void toHashMap(HashMap& hash_map, const T& value)
|
||||
// {
|
||||
// hashMapConstructor::construct(hash_map, value);
|
||||
// }
|
||||
|
||||
struct toHashMapFunction {
|
||||
template<typename HashMapPtr, typename T>
|
||||
auto operator()(HashMapPtr& hash_map, T&& value) const
|
||||
{
|
||||
return to_hash_map(hash_map, std::forward<T>(value));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Anonymous namespace prevents multiple definition of the reference
|
||||
namespace {
|
||||
// Function object
|
||||
constexpr const auto& to_hash_map = ruc::detail::staticConst<detail::toHashMapFunction>; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable)
|
||||
} // namespace
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
// Customization Points
|
||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
||||
|
||||
// blaze::to_hash_map is a function object, the type of which is
|
||||
// blaze::detail::toHashMapFunction. In the blaze::detail namespace are the to_hash_map
|
||||
// free functions. The function call operator of toHashMapFunction makes an
|
||||
// unqualified call to to_hash_map which, since it shares the detail namespace with
|
||||
// the to_hash_map free functions, will consider those in addition to any overloads
|
||||
// that are found by argument-dependent lookup.
|
||||
|
||||
// Variable templates are linked externally, therefor every translation unit
|
||||
// will see the same address for detail::staticConst<detail::toHashMapFunction>.
|
||||
// Since blaze::to_hash_map is a reference to the variable template, it too will have
|
||||
// the same address in all translation units.
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory> // std::static_pointer_cast
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "blaze/error.h"
|
||||
#include "blaze/types.h"
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// TODO: Move these ruc/test/macro.h -> ruc/src/meta/macro.h
|
||||
#define GET_2TH_ARG(arg1, arg2, ...) arg2
|
||||
#define GET_3TH_ARG(arg1, arg2, arg3, ...) arg3
|
||||
#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4
|
||||
#define GET_5TH_ARG(arg1, arg2, arg3, arg4, arg5, ...) arg5
|
||||
#define GET_6TH_ARG(arg1, arg2, arg3, arg4, arg5, arg6, ...) arg6
|
||||
#define MACRO_CHOOSER_1(macro, ...) \
|
||||
GET_2TH_ARG(__VA_ARGS__, macro##_1, )
|
||||
#define MACRO_CHOOSER_2(macro, ...) \
|
||||
GET_3TH_ARG(__VA_ARGS__, macro##_2, macro##_1, )
|
||||
#define MACRO_CHOOSER_3(macro, ...) \
|
||||
GET_4TH_ARG(__VA_ARGS__, macro##_3, macro##_2, macro##_1, )
|
||||
#define MACRO_CHOOSER_4(macro, ...) \
|
||||
GET_5TH_ARG(__VA_ARGS__, macro##_4, macro##_3, macro##_2, macro##_1, )
|
||||
#define MACRO_CHOOSER_5(macro, ...) \
|
||||
GET_6TH_ARG(__VA_ARGS__, macro##_5, macro##_4, macro##_3, macro##_2, macro##_1, )
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
#define CHECK_ARG_COUNT_IS_IMPL(name, size, expected, result) \
|
||||
if (size != expected) { \
|
||||
blaze::Error::the().add(::format("wrong number of arguments: {}, {}", name, size)); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
#define CHECK_ARG_COUNT_IS_3(name, size, expected) \
|
||||
CHECK_ARG_COUNT_IS_IMPL(name, size, expected, nullptr)
|
||||
|
||||
#define CHECK_ARG_COUNT_IS_4(name, size, expected, result) \
|
||||
CHECK_ARG_COUNT_IS_IMPL(name, size, expected, result)
|
||||
|
||||
#define CHECK_ARG_COUNT_IS(...) \
|
||||
MACRO_CHOOSER_4(CHECK_ARG_COUNT_IS, __VA_ARGS__) \
|
||||
(__VA_ARGS__)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
#define CHECK_ARG_COUNT_AT_LEAST_IMPL(name, size, min, result) \
|
||||
if (size < min) { \
|
||||
blaze::Error::the().add(::format("wrong number of arguments: {}, {}", name, size)); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
#define CHECK_ARG_COUNT_AT_LEAST_3(name, size, min) \
|
||||
CHECK_ARG_COUNT_AT_LEAST_IMPL(name, size, min, nullptr)
|
||||
|
||||
#define CHECK_ARG_COUNT_AT_LEAST_4(name, size, min, result) \
|
||||
CHECK_ARG_COUNT_AT_LEAST_IMPL(name, size, min, result)
|
||||
|
||||
#define CHECK_ARG_COUNT_AT_LEAST(...) \
|
||||
MACRO_CHOOSER_4(CHECK_ARG_COUNT_AT_LEAST, __VA_ARGS__) \
|
||||
(__VA_ARGS__)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
#define CHECK_ARG_COUNT_BETWEEN_IMPL(name, size, min, max, result) \
|
||||
if (size < min || size > max) { \
|
||||
blaze::Error::the().add(::format("wrong number of arguments: {}, {}", name, size)); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
#define CHECK_ARG_COUNT_BETWEEN_4(name, size, min, max) \
|
||||
CHECK_ARG_COUNT_BETWEEN_IMPL(name, size, min, max, nullptr)
|
||||
|
||||
#define CHECK_ARG_COUNT_BETWEEN_5(name, size, min, max, result) \
|
||||
CHECK_ARG_COUNT_BETWEEN_IMPL(name, size, min, max, result)
|
||||
|
||||
#define CHECK_ARG_COUNT_BETWEEN(...) \
|
||||
MACRO_CHOOSER_5(CHECK_ARG_COUNT_BETWEEN, __VA_ARGS__) \
|
||||
(__VA_ARGS__)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
#define CHECK_ARG_COUNT_EVEN_IMPL(name, size, result) \
|
||||
if (size % 2 != 0) { \
|
||||
blaze::Error::the().add(::format("wrong number of arguments: {}, {}", name, size)); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
#define CHECK_ARG_COUNT_EVEN_2(name, size) \
|
||||
CHECK_ARG_COUNT_EVEN_IMPL(name, size, nullptr)
|
||||
|
||||
#define CHECK_ARG_COUNT_EVEN_3(name, size, result) \
|
||||
CHECK_ARG_COUNT_EVEN_IMPL(name, size, result)
|
||||
|
||||
#define CHECK_ARG_COUNT_EVEN(...) \
|
||||
MACRO_CHOOSER_3(CHECK_ARG_COUNT_EVEN, __VA_ARGS__) \
|
||||
(__VA_ARGS__)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
#define IS_VALUE_IMPL(type, value, result) \
|
||||
if (!is<type>(value.get())) { \
|
||||
blaze::Error::the().add(::format("wrong argument type: {}, {}", #type, value)); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
#define IS_VALUE_2(type, value) \
|
||||
IS_VALUE_IMPL(type, value, nullptr)
|
||||
|
||||
#define IS_VALUE_3(type, value, result) \
|
||||
IS_VALUE_IMPL(type, value, result)
|
||||
|
||||
#define IS_VALUE(...) \
|
||||
MACRO_CHOOSER_3(IS_VALUE, __VA_ARGS__) \
|
||||
(__VA_ARGS__)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
#define VALUE_CAST_IMPL(variable, type, value, result) \
|
||||
IS_VALUE(type, value, result); \
|
||||
auto variable = std::static_pointer_cast<type>(value);
|
||||
|
||||
#define VALUE_CAST_3(variable, type, value) \
|
||||
VALUE_CAST_IMPL(variable, type, value, nullptr)
|
||||
|
||||
#define VALUE_CAST_4(variable, type, value, result) \
|
||||
VALUE_CAST_IMPL(variable, type, value, result)
|
||||
|
||||
#define VALUE_CAST(...) \
|
||||
MACRO_CHOOSER_4(VALUE_CAST, __VA_ARGS__) \
|
||||
(__VA_ARGS__)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
namespace blaze {
|
||||
|
||||
template<typename It, typename C>
|
||||
inline bool isLast(It it, const C& container)
|
||||
{
|
||||
return (it != container.end()) && (next(it) == container.end());
|
||||
}
|
||||
|
||||
inline std::string replaceAll(std::string text, std::string_view search, std::string_view replace)
|
||||
{
|
||||
size_t search_length = search.length();
|
||||
size_t replace_length = replace.length();
|
||||
size_t position = text.find(search, 0);
|
||||
while (position != std::string::npos) {
|
||||
text.replace(position, search_length, replace);
|
||||
position += replace_length;
|
||||
position = text.find(search, position);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "lexer.h"
|
||||
#include "ruc/format/print.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "printer.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
Printer::Printer(ASTNode* node)
|
||||
: m_node(node)
|
||||
{
|
||||
}
|
||||
|
||||
Printer::~Printer()
|
||||
{
|
||||
delete m_node;
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Printer::dump()
|
||||
{
|
||||
if (Error::the().hasAnyError()) {
|
||||
dumpError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_node == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
dumpImpl(m_node);
|
||||
print("\n");
|
||||
}
|
||||
|
||||
void Printer::dumpImpl(ASTNode* node)
|
||||
{
|
||||
auto printSpacing = [this]() -> void {
|
||||
if (!m_firstNode && !m_previousNodeIsList) {
|
||||
print(" ");
|
||||
}
|
||||
};
|
||||
|
||||
if (is<Vector>(node)) {
|
||||
printSpacing();
|
||||
print("[");
|
||||
m_firstNode = false;
|
||||
m_previousNodeIsList = true;
|
||||
Vector* vector = static_cast<Vector*>(node);
|
||||
for (size_t i = 0; i < vector->nodes().size(); ++i) {
|
||||
dumpImpl(vector->nodes()[i]);
|
||||
m_previousNodeIsList = false;
|
||||
}
|
||||
print("]");
|
||||
}
|
||||
else if (is<HashMap>(node)) {
|
||||
printSpacing();
|
||||
print("{{");
|
||||
m_firstNode = false;
|
||||
m_previousNodeIsList = true;
|
||||
HashMap* hash_map = static_cast<HashMap*>(node);
|
||||
for (size_t i = 0; i < hash_map->nodes().size(); ++i) {
|
||||
dumpImpl(hash_map->nodes()[i]);
|
||||
m_previousNodeIsList = false;
|
||||
}
|
||||
print("}}");
|
||||
}
|
||||
else if (is<List>(node)) {
|
||||
printSpacing();
|
||||
print("(");
|
||||
m_firstNode = false;
|
||||
m_previousNodeIsList = true;
|
||||
List* list = static_cast<List*>(node);
|
||||
for (size_t i = 0; i < list->nodes().size(); ++i) {
|
||||
dumpImpl(list->nodes()[i]);
|
||||
m_previousNodeIsList = false;
|
||||
}
|
||||
print(")");
|
||||
}
|
||||
else if (is<String>(node)) {
|
||||
printSpacing();
|
||||
print("{}", static_cast<String*>(node)->data());
|
||||
}
|
||||
else if (is<Number>(node)) {
|
||||
printSpacing();
|
||||
print("{}", static_cast<Number*>(node)->number());
|
||||
}
|
||||
else if (is<Symbol>(node)) {
|
||||
printSpacing();
|
||||
print("{}", static_cast<Symbol*>(node)->symbol());
|
||||
}
|
||||
}
|
||||
|
||||
void Printer::dumpError()
|
||||
{
|
||||
print("Error: ");
|
||||
if (Error::the().hasTokenError()) {
|
||||
Token error = Error::the().tokenError();
|
||||
print("{}", error.symbol);
|
||||
}
|
||||
else if (Error::the().hasOtherError()) {
|
||||
std::string error = Error::the().otherError();
|
||||
print("{}", error);
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ast.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
// Serializer -> return to string
|
||||
class Printer {
|
||||
public:
|
||||
Printer(ASTNode* node);
|
||||
virtual ~Printer();
|
||||
|
||||
void dump();
|
||||
|
||||
private:
|
||||
void dumpImpl(ASTNode* node);
|
||||
void dumpError();
|
||||
|
||||
bool m_firstNode { true };
|
||||
bool m_previousNodeIsList { false };
|
||||
ASTNode* m_node { nullptr };
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
-398
@@ -1,398 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // std::strtoll
|
||||
#include <utility> // std::move
|
||||
|
||||
#include "error.h"
|
||||
#include "ruc/format/color.h"
|
||||
#include "ruc/meta/assert.h"
|
||||
|
||||
#include "ast.h"
|
||||
#include "reader.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
Reader::Reader(std::vector<Token>&& tokens) noexcept
|
||||
: m_tokens(std::move(tokens))
|
||||
{
|
||||
}
|
||||
|
||||
Reader::~Reader()
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Reader::read()
|
||||
{
|
||||
if (Error::the().hasAnyError() || m_node) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_node = readImpl();
|
||||
|
||||
// Error checking
|
||||
|
||||
if (m_invalid_syntax) {
|
||||
Error::the().addError("invalid read syntax: '" + std::string(1, m_error_character) + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_is_unbalanced) {
|
||||
Error::the().addError("expected '" + std::string(1, m_error_character) + "', got EOF");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isEOF()) {
|
||||
Token::Type type = peek().type;
|
||||
switch (type) {
|
||||
case Token::Type::ParenOpen: // (
|
||||
case Token::Type::ParenClose: // )
|
||||
case Token::Type::String:
|
||||
case Token::Type::Value:
|
||||
Error::the().addError("more than one sexp in input");
|
||||
break;
|
||||
case Token::Type::Comment:
|
||||
break;
|
||||
default:
|
||||
Error::the().addError("unknown error");
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode* Reader::readImpl()
|
||||
{
|
||||
if (m_tokens.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
switch (peek().type) {
|
||||
case Token::Type::Special: // ~@
|
||||
return readSpliceUnquote();
|
||||
break;
|
||||
case Token::Type::BracketOpen: // [
|
||||
return readVector();
|
||||
break;
|
||||
case Token::Type::BracketClose: // ]
|
||||
m_invalid_syntax = true;
|
||||
m_error_character = ']';
|
||||
return nullptr;
|
||||
break;
|
||||
case Token::Type::BraceOpen: // {
|
||||
return readHashMap();
|
||||
break;
|
||||
case Token::Type::BraceClose: // }
|
||||
m_invalid_syntax = true;
|
||||
m_error_character = '}';
|
||||
return nullptr;
|
||||
break;
|
||||
case Token::Type::ParenOpen: // (
|
||||
return readList();
|
||||
break;
|
||||
case Token::Type::ParenClose: // )
|
||||
m_invalid_syntax = true;
|
||||
m_error_character = ')';
|
||||
return nullptr;
|
||||
break;
|
||||
case Token::Type::Quote: // '
|
||||
return readQuote();
|
||||
break;
|
||||
case Token::Type::Backtick: // `
|
||||
return readQuasiQuote();
|
||||
break;
|
||||
case Token::Type::Tilde: // ~
|
||||
return readUnquote();
|
||||
break;
|
||||
case Token::Type::Caret: // ^
|
||||
return readWithMeta();
|
||||
break;
|
||||
case Token::Type::At: // @
|
||||
return readDeref();
|
||||
break;
|
||||
case Token::Type::String:
|
||||
return readString();
|
||||
break;
|
||||
case Token::Type::Comment: // ;
|
||||
ignore();
|
||||
return nullptr;
|
||||
break;
|
||||
case Token::Type::Value:
|
||||
return readValue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
// Unimplemented token
|
||||
VERIFY_NOT_REACHED();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readSpliceUnquote()
|
||||
{
|
||||
ignore(); // ~@
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().addError("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
List* list = new List();
|
||||
list->addNode(new Symbol("splice-unquote"));
|
||||
list->addNode(readImpl());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readVector()
|
||||
{
|
||||
ignore(); // [
|
||||
|
||||
Vector* vector = new Vector();
|
||||
while (!isEOF() && peek().type != Token::Type::BracketClose) {
|
||||
vector->addNode(readImpl());
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::BracketClose })) { // ]
|
||||
m_error_character = ']';
|
||||
m_is_unbalanced = true;
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readHashMap()
|
||||
{
|
||||
ignore(); // {
|
||||
|
||||
HashMap* vector = new HashMap();
|
||||
while (!isEOF() && peek().type != Token::Type::BraceClose) {
|
||||
vector->addNode(readImpl());
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::BraceClose })) { // }
|
||||
m_error_character = '}';
|
||||
m_is_unbalanced = true;
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readList()
|
||||
{
|
||||
ignore(); // (
|
||||
|
||||
List* list = new List();
|
||||
while (!isEOF() && peek().type != Token::Type::ParenClose) {
|
||||
list->addNode(readImpl());
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::ParenClose })) { // )
|
||||
m_error_character = ')';
|
||||
m_is_unbalanced = true;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readQuote()
|
||||
{
|
||||
ignore(); // '
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().addError("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
List* list = new List();
|
||||
list->addNode(new Symbol("quote"));
|
||||
list->addNode(readImpl());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readQuasiQuote()
|
||||
{
|
||||
ignore(); // `
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().addError("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
List* list = new List();
|
||||
list->addNode(new Symbol("quasiquote"));
|
||||
list->addNode(readImpl());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readUnquote()
|
||||
{
|
||||
ignore(); // ~
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().addError("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
List* list = new List();
|
||||
list->addNode(new Symbol("unquote"));
|
||||
list->addNode(readImpl());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readWithMeta()
|
||||
{
|
||||
ignore(); // ^
|
||||
|
||||
ignore(); // first token
|
||||
if (isEOF()) { // second token
|
||||
Error::the().addError("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
retreat();
|
||||
|
||||
List* list = new List();
|
||||
list->addNode(new Symbol("with-meta"));
|
||||
ASTNode* first = readImpl();
|
||||
ASTNode* second = readImpl();
|
||||
list->addNode(second);
|
||||
list->addNode(first);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readDeref()
|
||||
{
|
||||
ignore(); // @
|
||||
|
||||
if (isEOF()) {
|
||||
Error::the().addError("expected form, got EOF");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
List* list = new List();
|
||||
list->addNode(new Symbol("deref"));
|
||||
list->addNode(readImpl());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readString()
|
||||
{
|
||||
std::string symbol = consume().symbol;
|
||||
|
||||
// Unbalanced string
|
||||
if (symbol.size() < 2 || symbol.front() != '"' || symbol.back() != '"') {
|
||||
m_error_character = '"';
|
||||
m_is_unbalanced = true;
|
||||
}
|
||||
|
||||
return new String(symbol);
|
||||
}
|
||||
|
||||
ASTNode* Reader::readValue()
|
||||
{
|
||||
Token token = consume();
|
||||
char* end_ptr = nullptr;
|
||||
int64_t result = std::strtoll(token.symbol.c_str(), &end_ptr, 10);
|
||||
if (end_ptr == token.symbol.c_str() + token.symbol.size()) {
|
||||
return new Number(result);
|
||||
}
|
||||
|
||||
return new Symbol(token.symbol);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
bool Reader::isEOF() const
|
||||
{
|
||||
return m_index >= m_tokens.size();
|
||||
}
|
||||
|
||||
Token Reader::peek() const
|
||||
{
|
||||
VERIFY(!isEOF());
|
||||
return m_tokens[m_index];
|
||||
}
|
||||
|
||||
Token Reader::consume()
|
||||
{
|
||||
VERIFY(!isEOF());
|
||||
return m_tokens[m_index++];
|
||||
}
|
||||
|
||||
bool Reader::consumeSpecific(Token token)
|
||||
{
|
||||
if (isEOF() || peek().type != token.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ignore();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reader::ignore()
|
||||
{
|
||||
m_index++;
|
||||
}
|
||||
|
||||
void Reader::retreat()
|
||||
{
|
||||
m_index--;
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Reader::dump()
|
||||
{
|
||||
dumpImpl(m_node);
|
||||
}
|
||||
|
||||
void Reader::dumpImpl(ASTNode* node)
|
||||
{
|
||||
std::string indentation = std::string(m_indentation * 2, ' ');
|
||||
|
||||
if (is<List>(node)) {
|
||||
List* list = static_cast<List*>(node);
|
||||
print("{}", indentation);
|
||||
print(fg(ruc::format::TerminalColor::Blue), "ListContainer");
|
||||
print(" <");
|
||||
print(fg(ruc::format::TerminalColor::Blue), "()");
|
||||
print(">\n");
|
||||
m_indentation++;
|
||||
for (size_t i = 0; i < list->nodes().size(); ++i) {
|
||||
dumpImpl(list->nodes()[i]);
|
||||
}
|
||||
m_indentation--;
|
||||
return;
|
||||
}
|
||||
else if (is<String>(node)) {
|
||||
print("{}", indentation);
|
||||
print(fg(ruc::format::TerminalColor::Yellow), "StringNode");
|
||||
print(" <{}>", static_cast<String*>(node)->data());
|
||||
}
|
||||
else if (is<Number>(node)) {
|
||||
print("{}", indentation);
|
||||
print(fg(ruc::format::TerminalColor::Yellow), "NumberNode");
|
||||
print(" <{}>", static_cast<Number*>(node)->number());
|
||||
}
|
||||
else if (is<Symbol>(node)) {
|
||||
print("{}", indentation);
|
||||
print(fg(ruc::format::TerminalColor::Yellow), "SymbolNode");
|
||||
print(" <{}>", static_cast<Symbol*>(node)->symbol());
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <vector>
|
||||
|
||||
#include "ast.h"
|
||||
#include "lexer.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
// Parsing -> creates AST
|
||||
class Reader {
|
||||
public:
|
||||
Reader(std::vector<Token>&& tokens) noexcept;
|
||||
virtual ~Reader();
|
||||
|
||||
void read();
|
||||
|
||||
void dump();
|
||||
|
||||
ASTNode* node() { return m_node; }
|
||||
|
||||
private:
|
||||
bool isEOF() const;
|
||||
Token peek() const;
|
||||
Token consume();
|
||||
bool consumeSpecific(Token token);
|
||||
void ignore();
|
||||
void retreat();
|
||||
|
||||
ASTNode* readImpl();
|
||||
ASTNode* readSpliceUnquote(); // ~@
|
||||
ASTNode* readVector(); // []
|
||||
ASTNode* readHashMap(); // {}
|
||||
ASTNode* readList(); // ()
|
||||
ASTNode* readQuote(); // '
|
||||
ASTNode* readQuasiQuote(); // `
|
||||
ASTNode* readUnquote(); // ~
|
||||
ASTNode* readWithMeta(); // ^
|
||||
ASTNode* readDeref(); // @
|
||||
ASTNode* readString(); // "foobar"
|
||||
ASTNode* readValue();
|
||||
|
||||
void dumpImpl(ASTNode* node);
|
||||
|
||||
size_t m_index { 0 };
|
||||
size_t m_indentation { 0 };
|
||||
std::vector<Token> m_tokens;
|
||||
|
||||
char m_error_character { 0 };
|
||||
bool m_invalid_syntax { false };
|
||||
bool m_is_unbalanced { false };
|
||||
|
||||
ASTNode* m_node { nullptr };
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
@@ -1,44 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <iostream> // std::cin
|
||||
#include <string> // std::getline
|
||||
#include <string_view>
|
||||
|
||||
#if 0
|
||||
auto read(std::string_view data) -> std::string_view
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
auto eval(std::string_view data) -> std::string_view
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
auto print(std::string_view data) -> void
|
||||
{
|
||||
printf("%s\n", data.data());
|
||||
}
|
||||
|
||||
auto rep(std::string_view data) -> void
|
||||
{
|
||||
print(eval(read(data)));
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
while (true) {
|
||||
printf("user> ");
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
|
||||
// Exit with Ctrl-D
|
||||
if (std::cin.eof() || std::cin.fail()) {
|
||||
break;
|
||||
}
|
||||
|
||||
rep(line);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,85 +0,0 @@
|
||||
#include <csignal> // std::signal
|
||||
#include <cstdlib> // std::exit
|
||||
#include <iostream> // std::cin
|
||||
#include <string> // std::getline
|
||||
#include <string_view>
|
||||
|
||||
#include "error.h"
|
||||
#include "ruc/format/color.h"
|
||||
|
||||
#include "ast.h"
|
||||
#include "lexer.h"
|
||||
#include "printer.h"
|
||||
#include "reader.h"
|
||||
|
||||
#define PRETTY_PRINT 0
|
||||
|
||||
#if 1
|
||||
auto read(std::string_view input) -> blaze::ASTNode*
|
||||
{
|
||||
blaze::Lexer lexer(input);
|
||||
lexer.tokenize();
|
||||
// lexer.dump();
|
||||
blaze::Reader reader(std::move(lexer.tokens()));
|
||||
reader.read();
|
||||
// reader.dump();
|
||||
|
||||
return reader.node();
|
||||
}
|
||||
|
||||
auto eval(blaze::ASTNode* node) -> blaze::ASTNode*
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
auto print(blaze::ASTNode* node) -> void
|
||||
{
|
||||
blaze::Printer printer(node);
|
||||
printer.dump();
|
||||
}
|
||||
|
||||
auto rep(std::string_view input) -> void
|
||||
{
|
||||
blaze::Error::the().clearErrors();
|
||||
blaze::Error::the().setInput(input);
|
||||
|
||||
print(eval(read(input)));
|
||||
}
|
||||
|
||||
static auto cleanup(int signal) -> void
|
||||
{
|
||||
print("\033[0m");
|
||||
std::exit(signal);
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
// Signal callbacks
|
||||
std::signal(SIGINT, cleanup);
|
||||
std::signal(SIGTERM, cleanup);
|
||||
|
||||
while (true) {
|
||||
#if PRETTY_PRINT
|
||||
print(fg(ruc::format::TerminalColor::Blue), "user>");
|
||||
print(" ");
|
||||
print("\033[1m");
|
||||
#else
|
||||
print("user> ");
|
||||
#endif
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
#if PRETTY_PRINT
|
||||
print("\033[0m");
|
||||
#endif
|
||||
|
||||
// Exit with Ctrl-D
|
||||
if (std::cin.eof() || std::cin.fail()) {
|
||||
break;
|
||||
}
|
||||
|
||||
rep(line);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
Vendored
+1
-1
Submodule vendor/ruc updated: 07c9f9959d...c8e4ae884e
Reference in New Issue
Block a user