Compare commits

..
107 Commits
Author SHA1 Message Date
Riyyi 79ede83a99 Meta: Improve project when used as a dependency 2023-12-18 18:17:10 +01:00
Riyyi 287c2457e7 Env: Add max/min/cos/sin math functions 2023-12-08 22:23:55 +01:00
Riyyi 092ada8479 Repl+Env+Example: Load global outer environment in Repl::init 2023-11-27 12:52:23 +01:00
Riyyi b68839902c Meta: Add needed LSP requirement 2023-11-27 12:50:55 +01:00
Riyyi f87eb4d934 Meta: Move main to example target, better include compatibility 2023-11-27 12:21:08 +01:00
Riyyi 11f0553b5a AST+Env+Printer+Reader: Implement floating point numbers 2023-11-26 10:45:45 +01:00
Riyyi 705e80ad6b Everywhere: Split REPL from main(), put settings in environment 2023-11-19 21:50:52 +01:00
Riyyi 4cc2acc8a0 Printer: Rename node -> value 2023-11-19 21:44:17 +01:00
Riyyi f5dc1168eb Reader+Env: Add dump function 2023-11-19 21:44:17 +01:00
Riyyi 536e55e75a Everywhere: Add docstring support 2023-11-19 21:44:17 +01:00
Riyyi 25871cd5d5 Print: Make strings brightgreen color 2023-11-14 23:30:28 +01:00
Riyyi 929fb5d645 Env+Eval: Tweak (and) and (-) 2023-11-14 23:29:06 +01:00
Riyyi 9db041946e Env: Add string/number conversion functions 2023-11-05 14:35:32 +01:00
Riyyi b74f3448b2 Eval: Allow multiple s-expr in fn* 2023-11-05 14:30:24 +01:00
Riyyi b727f7147e Eval: Add and/or special forms 2023-10-31 22:30:24 +01:00
Riyyi c6c6d69e73 Eval: Allow multiple s-expr in try* 2023-10-27 23:56:46 +02:00
Riyyi bb6f3e7496 Meta: Change README.org title and description 2023-10-27 23:33:45 +02:00
Riyyi 1915621427 Eval: Change (macroexpand) into (macroexpand-1) 2023-10-27 23:05:10 +02:00
Riyyi 9895195410 Env: Add support to (count) for HashMap type 2023-08-31 22:04:03 +02:00
Riyyi e8206d762c Env: Load lisp code at runtime from files 2023-08-31 21:58:44 +02:00
Riyyi b65482eb68 Env: Allow load order control for native functions 2023-08-27 21:38:54 +02:00
Riyyi 67b982fd4c Eval: Merge eval, eval-ast and macroexpand 2023-08-27 13:08:27 +02:00
Riyyi d3a50abfbc Env: Organize functions better by splitting into multiple files 2023-08-26 21:48:15 +02:00
Riyyi 80b25f8c21 Eval: Add special form while 2023-08-24 23:07:16 +02:00
Riyyi 0d43512ea9 Everywhere: Do less Collection nodes copying 2023-08-24 23:03:31 +02:00
Riyyi fa4bd63dca Main: Remove step mains 2023-08-24 22:54:52 +02:00
Riyyi 3aa99d0045 Meta: Add make targets for running self-hosted tests 2023-08-20 19:58:38 +02:00
Riyyi a099e955cf Eval: Fix bug with try* catch*, was set as TCO in main loop 2023-08-20 19:16:07 +02:00
Riyyi 63a19170ef Eval: Change stack-based TCO to loop-based TCO 2023-08-20 18:05:21 +02:00
Riyyi cf8e5dfc66 Everywhere: Remove HashMap::add() and remove(), as its not mutable 2023-08-17 22:59:40 +02:00
Riyyi 733d84c124 Main: Fix *ARGV* with 0 arguments 2023-08-17 22:02:10 +02:00
Riyyi ce0443a20e Everywhere: Remove Collection::add(), they are not mutable 2023-07-10 18:12:15 +02:00
Riyyi 6e6479bd14 Everywhere: Make the project compile again
std::format seems to finally have been implemented in the STL, which is
ambiguous with ruc::format even without ever including <format>.
2023-07-09 23:33:09 +02:00
Riyyi 453ca1f796 Lexer+Reader: Don't tokenize comments 2023-05-06 15:42:09 +02:00
Riyyi 534d80c35d Reader: Prevent infinite loop during List and Vector creation 2023-05-06 14:04:58 +02:00
Riyyi 099cda8b86 Main+Meta: Allow compilation of each step without source changes 2023-05-05 23:39:54 +02:00
Riyyi 4fff2b9aa2 Everywhere: Rename ValueList -> ValueVector 2023-05-01 18:28:48 +02:00
Riyyi 63c31d7ccf Everywhere: Convert List and Vector back to std::vector, ~+20% speed 2023-05-01 18:28:43 +02:00
Riyyi 12d6b8ec88 Everywhere: Change macro into a separate type 2023-05-01 10:24:38 +02:00
Riyyi f796f9c685 AST+Env: Implement stepA 2023-04-30 22:55:34 +02:00
Riyyi 86e1a630b8 AST+Eval: Prevent list copy during core function 'rest' 2023-04-23 16:00:13 +02:00
Riyyi d34ab1efab AST+Eval: Prevent copying lists where unneeded 2023-04-23 14:42:38 +02:00
Riyyi 24eab1034e Eval: Speed up isMacroCall a bit 2023-04-23 11:31:59 +02:00
Riyyi 6e0fe3a2f5 Main+Readline+Env: Add readline core function 2023-04-17 22:18:03 +02:00
Riyyi 7d91ef9198 Meta: Update README.org 2023-04-17 16:17:58 +02:00
Riyyi 76eb4204be Meta: Update README.org 2023-04-17 16:02:04 +02:00
Riyyi fc76da4ced Meta: Add README.org 2023-04-17 15:52:03 +02:00
Riyyi 88d9253219 Main: Fix compilation of older steps 2023-04-17 15:45:56 +02:00
Riyyi fa79f1296a Main: Fix extra newline on empty input in readline 2023-04-17 14:56:58 +02:00
Riyyi bb4ccc5822 Everywhere: Implement step9 try/catch 2023-04-17 14:44:46 +02:00
Riyyi 8aa540c4e5 Env: Core function (get) should work on nil value 2023-04-17 14:14:55 +02:00
Riyyi e09f168d4f AST: HashMap inserts should overwrite existing keys 2023-04-17 14:13:35 +02:00
Riyyi 6980d28b36 Main: Add step9 file 2023-04-15 20:19:49 +02:00
Riyyi 2a16f5ddf5 AST+Env: Add more core functions 2023-04-15 20:19:24 +02:00
Riyyi f89e73d44a AST+Env: Add default constructor to Constant 2023-04-15 15:04:23 +02:00
Riyyi c8b1e6dc81 AST+Lexer: Move Keyword prefix character to the constructor 2023-04-12 23:03:43 +02:00
Riyyi c279f9111e Eval: Only need to cast AST once 2023-04-10 22:06:36 +02:00
Riyyi 2b260d7b43 Everywhere: Implement step8 2023-04-10 21:23:49 +02:00
Riyyi 27d6e24243 Eval+Env: Make use of macros for duplicate logic 2023-04-10 13:59:48 +02:00
Riyyi b4742ef380 Reader: Improve HashMap logic 2023-04-10 11:34:56 +02:00
Riyyi 082a029957 AST+Eval: Provide convenient constructor for Collection 2023-04-09 21:54:01 +02:00
Riyyi 80e0437a5c Main+Eval+AST: Implement step7 2023-04-09 11:48:51 +02:00
Riyyi ba7281b6f1 Main: Add ability to run script from the command line, add *ARGV* 2023-04-08 00:29:03 +02:00
Riyyi b9feb8e8b6 Everywhere: Rename Value -> Constant, ASTNode -> Value 2023-04-07 23:38:41 +02:00
Riyyi e39862fe9f Meta: Add symlink to pass more tests 2023-04-07 23:38:41 +02:00
Riyyi 93af6151fa Env: Add more core functions 2023-04-07 23:38:41 +02:00
Riyyi 9b14ea974a AST: Make parent type for Function and Lambda 2023-04-07 23:38:41 +02:00
Riyyi 25d6f45329 AST: Do not try to store nullptr in a Collection or HashMap 2023-04-07 23:38:41 +02:00
Riyyi 835669c4eb AST: Add Atom data type 2023-04-07 23:38:41 +02:00
Riyyi 22920cb810 Main+Env: Add more native functions 2023-04-07 23:38:41 +02:00
Riyyi 3d5a2e3fbe AST: Make fastIf functions private 2023-04-07 23:38:41 +02:00
Riyyi 088c5f0248 Lexer: Support newline characters in strings 2023-04-07 23:38:41 +02:00
Riyyi 03918b036d AST: Rename function addElement -> add 2023-04-07 23:38:41 +02:00
Riyyi 5f49f08eea AST: Make more const 2023-04-07 23:38:41 +02:00
Riyyi ed9fa1698e Main: Add step6 .cpp file 2023-04-07 23:38:41 +02:00
Riyyi f35fea9fa4 Env: Fix function argument size error message 2023-04-07 23:38:41 +02:00
Riyyi 6e1557ff8c Printer: Fix HashMap printing 2023-04-07 23:38:41 +02:00
Riyyi 21f0d8fbd8 Eval: Move logic from defines back into functions 2023-04-03 23:17:24 +02:00
Riyyi 4d3c2a4ca2 Eval: Implement tail call optimization (TCO) via stack iteration 2023-04-03 23:02:04 +02:00
Riyyi 7c62d65d72 Main+Eval: Implement tail call optimization (TCO) 2023-04-03 21:42:11 +02:00
Riyyi f0f4ba45b0 Eval: Improve logic sharing between List and Vector 2023-04-03 21:42:11 +02:00
Riyyi 826af24561 Main+Env+Printer: Remove the GlobalEnvironment subclass 2023-04-02 21:06:32 +02:00
Riyyi c53c0af5a2 Everywhere: Rename addNode and addError functions -> add 2023-04-02 15:30:26 +02:00
Riyyi 14367fa5a7 Eval+Env: Add support for variadic lambda parameters 2023-04-02 15:27:18 +02:00
Riyyi e47b338984 Eval: Forward declare List 2023-04-02 12:12:33 +02:00
Riyyi 30b120befc Eval+Env: Improve logic sharing between List and Vector 2023-04-02 12:12:33 +02:00
Riyyi 90d6ff7d18 Printer: Pass along readably flag during recursion 2023-04-02 11:05:01 +02:00
Riyyi a60859acc4 Eval+Env: Add support for lambdas 2023-04-01 23:37:45 +02:00
Riyyi 1e76d4599a Meta+Env: Fix count on nil value 2023-03-28 22:54:28 +02:00
Riyyi bbced6f487 Eval: Add special form "if" 2023-03-28 22:50:57 +02:00
Riyyi 38d8daa9d0 Eval: Add special form "do" 2023-03-28 22:22:03 +02:00
Riyyi 35a32678d0 Everywhere: Convert List and Vector to an std::list<> datatype 2023-03-28 22:13:41 +02:00
Riyyi de2a207fcb AST: Reorder classes 2023-03-28 20:17:03 +02:00
Riyyi 9c1c5114a9 Env: Add equal function 2023-03-28 20:15:38 +02:00
Riyyi c1e4b6c6d1 Lexer+Printer: Support string print readably 2023-03-27 21:57:18 +02:00
Riyyi 6573ac0b22 Env: Add more native functions 2023-03-26 23:22:02 +02:00
Riyyi 424bbcc834 Everywhere: Add support for nil, true, false data types 2023-03-26 22:38:53 +02:00
Riyyi f132397e15 Eval: Improve correctness and error handling 2023-03-26 20:14:13 +02:00
Riyyi 58584f5bba Everywhere: Initial implementation of step3 2023-03-26 14:57:56 +02:00
Riyyi f4e388716d Meta: Improve formatting 2023-03-26 14:56:25 +02:00
Riyyi aba70beeb3 Main+Readline: Do not leak history path std::string_view 2023-03-25 22:36:02 +01:00
Riyyi 9bbf238c34 Everywhere: Put ASTNodes into a smart pointer 2023-03-25 21:35:17 +01:00
Riyyi 441fdc0564 Main: Fix history path option 2023-03-25 20:07:36 +01:00
Riyyi d772495f1a Printer: Return string instead of printing directly 2023-03-25 20:01:30 +01:00
Riyyi cc735a7a11 Eval: Fix mal test
The quote in the error message broke the step2 test.
2023-03-25 17:49:54 +01:00
Riyyi a92eede277 Main: Use GNU Readline library as the line editor 2023-03-25 15:05:29 +01:00
Riyyi 9fa6314378 Reader: Improve error reporting 2023-03-25 10:53:15 +01:00
68 changed files with 5413 additions and 1609 deletions
+2
View File
@@ -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
+60 -19
View File
@@ -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,39 +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} -c
DEPENDS ${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")
add_custom_target(test2
COMMAND env STEP=step_eval MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step2_eval.mal -- ./${PROJECT})
add_dependencies(test2 ${PROJECT})
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()
+49
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
file(COPY ${CMAKE_CURRENT_LIST_DIR}/../lisp DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+1
View File
@@ -0,0 +1 @@
build/compile_commands.json
+25
View File
@@ -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 "..")
+93
View File
@@ -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);
}
Symlink
+1
View File
@@ -0,0 +1 @@
vendor/mal/impls/lib
+12
View File
@@ -0,0 +1,12 @@
(defmacro! cond (fn* [& xs]
(if (> (count xs) 0)
(list 'if (first xs)
(if (> (count xs) 1)
(nth xs 1)
(throw "odd number of forms to cond"))
(cons 'cond (rest (rest xs)))))))
;; Local Variables:
;; eval: (emacs-lisp-mode)
;; End:
+14
View File
@@ -0,0 +1,14 @@
(defmacro! defmacro
(fn* [name args & body]
`(defmacro! ~name (fn* ~args ~@body))))
(defmacro defn [name args & body]
`(def! ~name (fn* ~args ~@body)))
(defmacro def [name & body]
`(def! ~name ~@body))
;; Local Variables:
;; eval: (emacs-lisp-mode)
;; End:
+12
View File
@@ -0,0 +1,12 @@
(defn load-file [file]
"Load the Lisp file named FILE."
(eval (read-string (str "(do " (slurp file) "\nnil)"))))
(defn load [file]
"Load the Lisp file named FILE."
(eval (read-string (str "(let* [] (do " (slurp file) "))"))))
;; Local Variables:
;; eval: (emacs-lisp-mode)
;; End:
+6
View File
@@ -0,0 +1,6 @@
(def *host-language* "C++")
;; Local Variables:
;; eval: (emacs-lisp-mode)
;; End:
+8
View File
@@ -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:
Symlink
+1
View File
@@ -0,0 +1 @@
vendor/mal/impls/mal
-99
View File
@@ -1,99 +0,0 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstdint> // int64_t
#include <string>
#include "ast.h"
#include "types.h"
namespace blaze {
Collection::~Collection()
{
for (auto node : m_nodes) {
delete node;
}
}
void Collection::addNode(ASTNode* node)
{
m_nodes.push_back(node);
}
// -----------------------------------------
void HashMap::addElement(const std::string& key, ASTNode* value)
{
m_elements.emplace(key, value);
}
// -----------------------------------------
String::String(const std::string& data)
: m_data(data)
{
}
// -----------------------------------------
Keyword::Keyword(const std::string& data)
: m_data(data)
{
}
// -----------------------------------------
Number::Number(int64_t number)
: m_number(number)
{
}
// -----------------------------------------
Symbol::Symbol(const std::string& symbol)
: m_symbol(symbol)
{
}
// -----------------------------------------
Value::Value(const std::string& value)
: m_value(value)
{
}
// -----------------------------------------
Function::Function(Lambda lambda)
: m_lambda(lambda)
{
}
} // namespace blaze
// -----------------------------------------
void Formatter<blaze::ASTNode*>::format(Builder& builder, blaze::ASTNode* value) const
{
// TODO: Call into Printer::dumpImp(), instead of doing it manually
if (is<blaze::String>(value)) {
return Formatter<std::string>::format(builder, static_cast<blaze::String*>(value)->data());
}
if (is<blaze::Keyword>(value)) {
return Formatter<std::string>::format(builder, ":" + static_cast<blaze::Keyword*>(value)->keyword().substr(1));
}
else if (is<blaze::Number>(value)) {
Formatter<int64_t> formatter { .specifier = specifier };
return formatter.format(builder, static_cast<blaze::Number*>(value)->number());
}
else if (is<blaze::Value>(value)) {
return Formatter<std::string>::format(builder, static_cast<blaze::Value*>(value)->value());
}
else if (is<blaze::Symbol>(value)) {
return Formatter<std::string>::format(builder, static_cast<blaze::Symbol*>(value)->symbol());
}
}
-248
View File
@@ -1,248 +0,0 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint> // int64_t
#include <functional> // std::function
#include <span>
#include <string>
#include <string_view>
#include <typeinfo> // typeid
#include <unordered_map>
#include <vector>
#include "ruc/format/formatter.h"
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 isKeyword() const { return false; }
virtual bool isNumber() const { return false; }
virtual bool isValue() const { return false; }
virtual bool isSymbol() const { return false; }
virtual bool isFunction() 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; }
size_t size() const { return m_nodes.size(); }
bool empty() const { return m_nodes.size() == 0; }
protected:
Collection() {}
private:
std::vector<ASTNode*> m_nodes;
};
// -----------------------------------------
// ()
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; }
};
// -----------------------------------------
// []
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 ASTNode {
public:
HashMap() = default;
virtual ~HashMap() = default;
virtual bool isHashMap() const override { return true; }
void addElement(const std::string& key, ASTNode* value);
const std::unordered_map<std::string, ASTNode*>& elements() const { return m_elements; }
size_t size() const { return m_elements.size(); }
bool empty() const { return m_elements.size() == 0; }
private:
std::unordered_map<std::string, ASTNode*> m_elements;
};
// -----------------------------------------
// "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;
};
// -----------------------------------------
// :keyword
class Keyword final : public ASTNode {
public:
Keyword(const std::string& data);
virtual ~Keyword() = default;
virtual bool isKeyword() const override { return true; }
const std::string& keyword() 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 };
};
// -----------------------------------------
// Symbols
class Symbol final : public ASTNode {
public:
Symbol(const std::string& symbol);
virtual ~Symbol() = default;
virtual bool isSymbol() const override { return true; }
const std::string& symbol() const { return m_symbol; }
private:
std::string m_symbol;
};
// -----------------------------------------
// true, false, nil
class Value final : public ASTNode {
public:
Value(const std::string& value);
virtual ~Value() = default;
virtual bool isValue() const override { return true; }
const std::string& value() const { return m_value; }
private:
std::string m_value;
};
// -----------------------------------------
using Lambda = std::function<ASTNode*(std::span<ASTNode*>)>;
class Function final : public ASTNode {
public:
Function(Lambda lambda);
virtual ~Function() = default;
virtual bool isFunction() const override { return true; }
Lambda lambda() const { return m_lambda; }
private:
Lambda m_lambda;
};
// -----------------------------------------
// clang-format off
template<>
inline bool ASTNode::fastIs<Collection>() const { return isCollection(); }
template<>
inline bool ASTNode::fastIs<List>() const { return isList(); }
template<>
inline bool ASTNode::fastIs<Vector>() const { return isVector(); }
template<>
inline bool ASTNode::fastIs<HashMap>() const { return isHashMap(); }
template<>
inline bool ASTNode::fastIs<String>() const { return isString(); }
template<>
inline bool ASTNode::fastIs<Keyword>() const { return isKeyword(); }
template<>
inline bool ASTNode::fastIs<Number>() const { return isNumber(); }
template<>
inline bool ASTNode::fastIs<Symbol>() const { return isSymbol(); }
template<>
inline bool ASTNode::fastIs<Value>() const { return isValue(); }
template<>
inline bool ASTNode::fastIs<Function>() const { return isFunction(); }
// clang-format on
} // namespace blaze
// -----------------------------------------
template<>
struct ruc::format::Formatter<blaze::ASTNode*> : public Formatter<std::string> {
void format(Builder& builder, blaze::ASTNode* value) const;
};
+299
View File
@@ -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
View File
@@ -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;
};
+178
View File
@@ -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
+69
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+166
View File
@@ -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
+149
View File
@@ -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
+75
View File
@@ -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
+82
View File
@@ -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
+60
View File
@@ -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
+93
View File
@@ -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
View File
@@ -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
+78
View File
@@ -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
View File
@@ -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
+57
View File
@@ -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
+22
View File
@@ -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 -4
View File
@@ -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
+666
View File
@@ -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
+236
View File
@@ -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
+65
View File
@@ -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
+41
View File
@@ -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
+15 -29
View File
@@ -11,8 +11,8 @@
#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 {
@@ -124,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;
@@ -149,17 +146,19 @@ bool Lexer::consumeString()
break;
}
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 });
@@ -171,7 +170,6 @@ bool Lexer::consumeKeyword()
{
size_t column = m_column;
std::string keyword;
keyword += 0x7f; // 127
ignore(); // :
@@ -195,7 +193,7 @@ bool Lexer::consumeKeyword()
};
char character = 0;
for (;;) {
while (true) {
character = peek();
if (exit.find(character) != exit.end()) {
@@ -238,7 +236,7 @@ bool Lexer::consumeValue()
};
char character = 0;
for (;;) {
while (true) {
character = peek();
if (exit.find(character) != exit.end()) {
@@ -258,9 +256,6 @@ bool Lexer::consumeValue()
bool Lexer::consumeComment()
{
size_t column = m_column;
std::string comment;
ignore(); // ;
static std::unordered_set<char> exit = {
@@ -270,25 +265,16 @@ bool Lexer::consumeComment()
};
char character = 0;
for (;;) {
while (true) {
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;
}
+1 -2
View File
@@ -33,8 +33,7 @@ struct Token {
At, // @
String, // "foobar"
Keyword, // :keyword
Value, // numbers, "true", "false", and "nil", symbols
Comment, // ;
Value, // number, "nil", "true", "false", symbol
Error,
};
+24
View File
@@ -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__))
+186
View File
@@ -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
+34
View File
@@ -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
+509
View File
@@ -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
+68
View File
@@ -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
+75
View File
@@ -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
+33
View File
@@ -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
+106
View File
@@ -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
+31
View File
@@ -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
+30
View File
@@ -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
+4 -4
View File
@@ -6,12 +6,10 @@
#pragma once
#include "ruc/meta/assert.h"
#include "ruc/singleton.h"
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include "ruc/singleton.h"
namespace blaze {
@@ -23,6 +21,8 @@ public:
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;
};
+62
View File
@@ -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.
View File
+165
View File
@@ -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
-150
View File
@@ -1,150 +0,0 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint> // int64_t
#include <iostream>
#include <span>
#include <string_view>
#include <unordered_map>
#include <vector>
#include "error.h"
#include "ruc/format/color.h"
#include "ruc/singleton.h"
#include "ast.h"
#include "types.h"
namespace blaze {
class Environment {
public:
Environment() = default;
virtual ~Environment() = default;
ASTNode* lookup(const std::string& symbol)
{
m_current_key = symbol;
return m_values.find(symbol) != m_values.end() ? m_values[symbol] : nullptr;
}
protected:
std::string m_current_key;
std::unordered_map<std::string, ASTNode*> m_values;
};
class GlobalEnvironment final : public Environment {
public:
GlobalEnvironment()
{
// TODO: Add more native functions
// TODO: Move the functions to their own file
auto add = [](std::span<ASTNode*> nodes) -> ASTNode* {
int64_t result = 0;
for (auto node : nodes) {
if (!is<Number>(node)) {
Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node));
return nullptr;
}
result += static_cast<Number*>(node)->number();
}
return new Number(result);
};
auto sub = [](std::span<ASTNode*> nodes) -> ASTNode* {
int64_t result = 0;
if (nodes.size() == 0) {
return new Number(0);
}
for (auto node : nodes) {
if (!is<Number>(node)) {
Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node));
return nullptr;
}
}
// Start with the first number
result += static_cast<Number*>(nodes[0])->number();
// Skip the first node
for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) {
result -= static_cast<Number*>(*it)->number();
}
return new Number(result);
};
auto mul = [](std::span<ASTNode*> nodes) -> ASTNode* {
int64_t result = 1;
for (auto node : nodes) {
if (!is<Number>(node)) {
Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node));
return nullptr;
}
result *= static_cast<Number*>(node)->number();
}
return new Number(result);
};
auto div = [this](std::span<ASTNode*> nodes) -> ASTNode* {
double result = 0;
if (nodes.size() == 0) {
Error::the().addError(format("wrong number of arguments: {}, 0", m_current_key));
return nullptr;
}
for (auto node : nodes) {
if (!is<Number>(node)) {
Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node));
return nullptr;
}
}
// Start with the first number
result += static_cast<Number*>(nodes[0])->number();
// Skip the first node
for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) {
result /= static_cast<Number*>(*it)->number();
}
return new Number((int64_t)result);
};
m_values.emplace("+", new Function(add));
m_values.emplace("-", new Function(sub));
m_values.emplace("*", new Function(mul));
m_values.emplace("/", new Function(div));
}
virtual ~GlobalEnvironment() = default;
};
} // namespace blaze
// associative data structure that maps symbols (the keys) to values
// values = anything, including other symbols.
// an environment is like a hash table
// value can map to:
// list
// vector
// hash-map
// symbol
// number
// string
// function
-94
View File
@@ -1,94 +0,0 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <span> // std::span
#include "ast.h"
#include "environment.h"
#include "eval.h"
#include "ruc/meta/assert.h"
#include "types.h"
namespace blaze {
Eval::Eval(ASTNode* ast, Environment* env)
: m_ast(ast)
, m_env(env)
{
}
void Eval::eval()
{
m_ast = evalImpl(m_ast, m_env);
}
ASTNode* Eval::evalImpl(ASTNode* ast, Environment* env)
{
if (!is<List>(ast)) {
return evalAst(ast, env);
}
if (static_cast<List*>(ast)->empty()) {
return ast;
}
return apply(static_cast<List*>(evalAst(ast, env)));
}
ASTNode* Eval::evalAst(ASTNode* ast, Environment* env)
{
if (is<Symbol>(ast)) {
auto result = env->lookup(static_cast<Symbol*>(ast)->symbol());
if (!result) {
Error::the().addError(format("symbols function definition is void: {}", ast));
}
return result;
}
else if (is<List>(ast)) {
auto result = new List();
auto nodes = static_cast<List*>(ast)->nodes();
for (auto node : nodes) {
result->addNode(evalImpl(node, env));
}
return result;
}
else if (is<Vector>(ast)) {
auto result = new Vector();
auto nodes = static_cast<Vector*>(ast)->nodes();
for (auto node : nodes) {
result->addNode(evalImpl(node, env));
}
return result;
}
else if (is<HashMap>(ast)) {
auto result = new HashMap();
auto elements = static_cast<HashMap*>(ast)->elements();
for (auto& element : elements) {
result->addElement(element.first, evalImpl(element.second, env));
}
return result;
}
return ast;
}
ASTNode* Eval::apply(List* evaluated_list)
{
auto nodes = evaluated_list->nodes();
if (!is<Function>(nodes[0])) {
Error::the().addError(format("invalid function: {}", nodes[0]));
return nullptr;
}
// car
auto lambda = static_cast<Function*>(nodes[0])->lambda();
// cdr
std::span<ASTNode*> span { nodes.data() + 1, nodes.size() - 1 };
return lambda(span);
}
} // namespace blaze
-32
View File
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "ast.h"
#include "environment.h"
namespace blaze {
class Eval {
public:
Eval(ASTNode* ast, Environment* env);
virtual ~Eval() = default;
void eval();
ASTNode* ast() const { return m_ast; }
private:
ASTNode* evalImpl(ASTNode* ast, Environment* env);
ASTNode* evalAst(ASTNode* ast, Environment* env);
ASTNode* apply(List* evaluated_list);
ASTNode* m_ast { nullptr };
Environment* m_env { nullptr };
};
} // namespace blaze
-127
View File
@@ -1,127 +0,0 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <iterator> // std::next
#include "ruc/format/print.h"
#include "error.h"
#include "lexer.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<List>(node)) {
printSpacing();
print("(");
m_firstNode = false;
m_previousNodeIsList = true;
auto nodes = static_cast<List*>(node)->nodes();
for (size_t i = 0; i < nodes.size(); ++i) {
dumpImpl(nodes[i]);
m_previousNodeIsList = false;
}
print(")");
}
else if (is<Vector>(node)) {
printSpacing();
print("[");
m_firstNode = false;
m_previousNodeIsList = true;
auto nodes = static_cast<Vector*>(node)->nodes();
for (size_t i = 0; i < nodes.size(); ++i) {
dumpImpl(nodes[i]);
m_previousNodeIsList = false;
}
print("]");
}
else if (is<HashMap>(node)) {
printSpacing();
print("{{");
m_firstNode = false;
m_previousNodeIsList = true;
auto elements = static_cast<HashMap*>(node)->elements();
for (auto it = elements.begin(); it != elements.end(); ++it) {
print("{} ", it->first.front() == 0x7f ? ":" + it->first.substr(1) : it->first); // 127
dumpImpl(it->second);
if (it != elements.end() && std::next(it) != elements.end()) {
print(" ");
}
}
m_previousNodeIsList = false;
print("}}");
}
else if (is<String>(node)) {
// TODO: Implement string readably printing
printSpacing();
print("{}", static_cast<String*>(node)->data());
}
else if (is<Keyword>(node)) {
printSpacing();
print(":{}", static_cast<Keyword*>(node)->keyword().substr(1));
}
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
-30
View File
@@ -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
-425
View File
@@ -1,425 +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();
// TODO: Move these to the appropriate functions
// 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: // "foobar"
return readString();
break;
case Token::Type::Keyword: // :keyword
return readKeyword();
break;
case Token::Type::Comment: // ;
ignore();
return nullptr;
break;
case Token::Type::Value: // true, false, nil
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::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::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* hash_map = new HashMap();
while (!isEOF() && peek().type != Token::Type::BraceClose) {
ASTNode* key = readImpl();
ASTNode* value = readImpl();
if (!key && !value) {
break;
}
if (!key || !value) {
Error::the().addError("hash-map requires an even-sized list");
return nullptr;
}
if (!is<String>(key) && !is<Keyword>(key)) {
Error::the().addError(format("{} is not a string or keyword", key));
return nullptr;
}
std::string keyString = is<String>(key) ? static_cast<String*>(key)->data() : static_cast<Keyword*>(key)->keyword();
hash_map->addElement(keyString, value);
}
if (!consumeSpecific(Token { .type = Token::Type::BraceClose })) { // }
m_error_character = '}';
m_is_unbalanced = true;
}
return hash_map;
}
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::readKeyword()
{
return new Keyword(consume().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
-64
View File
@@ -1,64 +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* readList(); // ()
ASTNode* readVector(); // []
ASTNode* readHashMap(); // {}
ASTNode* readQuote(); // '
ASTNode* readQuasiQuote(); // `
ASTNode* readUnquote(); // ~
ASTNode* readWithMeta(); // ^
ASTNode* readDeref(); // @
ASTNode* readString(); // "foobar"
ASTNode* readKeyword(); // :keyword
ASTNode* readValue(); // true, false, nil
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
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include "settings.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);
};
} // namespace blaze
-44
View File
@@ -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
-106
View File
@@ -1,106 +0,0 @@
#include <csignal> // std::signal
#include <cstdlib> // std::exit
#include <iostream> // std::cin
#include <string> // std::getline
#include <string_view>
#include "ruc/argparser.h"
#include "ruc/format/color.h"
#include "ast.h"
#include "error.h"
#include "lexer.h"
#include "printer.h"
#include "reader.h"
#include "settings.h"
#if 0
auto read(std::string_view input) -> blaze::ASTNode*
{
blaze::Lexer lexer(input);
lexer.tokenize();
if (blaze::Settings::the().get("dump-lexer") == "1") {
lexer.dump();
}
blaze::Reader reader(std::move(lexer.tokens()));
reader.read();
if (blaze::Settings::the().get("dump-reader") == "1") {
reader.dump();
}
return reader.node();
}
auto eval(blaze::ASTNode* ast) -> blaze::ASTNode*
{
return ast;
}
auto print(blaze::ASTNode* exp) -> void
{
blaze::Printer printer(exp);
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 argc, char* argv[]) -> int
{
bool dump_lexer = false;
bool dump_reader = false;
bool pretty_print = false;
// 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.parse(argc, argv);
// Set settings
blaze::Settings::the().set("dump-lexer", dump_lexer ? "1" : "0");
blaze::Settings::the().set("dump-reader", dump_reader ? "1" : "0");
blaze::Settings::the().set("pretty-print", pretty_print ? "1" : "0");
// Signal callbacks
std::signal(SIGINT, cleanup);
std::signal(SIGTERM, cleanup);
while (true) {
if (!pretty_print) {
print("user> ");
}
else {
print(fg(ruc::format::TerminalColor::Blue), "user>");
print(" \033[1m");
}
std::string line;
std::getline(std::cin, line);
if (pretty_print) {
print("\033[0m");
}
// Exit with Ctrl-D
if (std::cin.eof() || std::cin.fail()) {
break;
}
rep(line);
}
return 0;
}
#endif
-113
View File
@@ -1,113 +0,0 @@
#include <csignal> // std::signal
#include <cstdlib> // std::exit
#include <iostream> // std::cin
#include <string> // std::getline
#include <string_view>
#include "environment.h"
#include "eval.h"
#include "ruc/argparser.h"
#include "ruc/format/color.h"
#include "ast.h"
#include "error.h"
#include "lexer.h"
#include "printer.h"
#include "reader.h"
#include "settings.h"
#if 1
auto read(std::string_view input) -> blaze::ASTNode*
{
blaze::Lexer lexer(input);
lexer.tokenize();
if (blaze::Settings::the().get("dump-lexer") == "1") {
lexer.dump();
}
blaze::Reader reader(std::move(lexer.tokens()));
reader.read();
if (blaze::Settings::the().get("dump-reader") == "1") {
reader.dump();
}
return reader.node();
}
auto eval(blaze::ASTNode* ast) -> blaze::ASTNode*
{
blaze::GlobalEnvironment env;
blaze::Eval eval(ast, &env);
eval.eval();
return eval.ast();
}
auto print(blaze::ASTNode* exp) -> void
{
blaze::Printer printer(exp);
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 argc, char* argv[]) -> int
{
bool dump_lexer = false;
bool dump_reader = false;
bool pretty_print = false;
// 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.parse(argc, argv);
// Set settings
blaze::Settings::the().set("dump-lexer", dump_lexer ? "1" : "0");
blaze::Settings::the().set("dump-reader", dump_reader ? "1" : "0");
blaze::Settings::the().set("pretty-print", pretty_print ? "1" : "0");
// Signal callbacks
std::signal(SIGINT, cleanup);
std::signal(SIGTERM, cleanup);
while (true) {
if (!pretty_print) {
print("user> ");
}
else {
print(fg(ruc::format::TerminalColor::Blue), "user>");
print(" \033[1m");
}
std::string line;
std::getline(std::cin, line);
if (pretty_print) {
print("\033[0m");
}
// Exit with Ctrl-D
if (std::cin.eof() || std::cin.fail()) {
break;
}
rep(line);
}
return 0;
}
#endif
// - Add AST node printing support to ruc::format
Symlink
+1
View File
@@ -0,0 +1 @@
vendor/mal/tests
Vendored
+1 -1
Submodule vendor/ruc updated: 07c9f9959d...c8e4ae884e