diff --git a/common/goos/Interpreter.cpp b/common/goos/Interpreter.cpp index 9c2ce5e56..b5fafcb99 100644 --- a/common/goos/Interpreter.cpp +++ b/common/goos/Interpreter.cpp @@ -6,10 +6,11 @@ #include #include "Interpreter.h" #include "ParseHelpers.h" +#include "common/util/FileUtil.h" #include namespace goos { -Interpreter::Interpreter() { +Interpreter::Interpreter(const std::string& username) { // Interpreter startup: goal_to_goos.reset(); @@ -21,9 +22,14 @@ Interpreter::Interpreter() { // make both environments available in both. define_var_in_env(global_environment, global_environment, "*global-env*"); + define_var_in_env(global_environment, goal_env, "*goal-env*"); define_var_in_env(goal_env, goal_env, "*goal-env*"); define_var_in_env(goal_env, global_environment, "*global-env*"); - define_var_in_env(global_environment, goal_env, "*goal-env*"); + + // set user profile name + auto user = SymbolObject::make_new(reader.symbolTable, username); + define_var_in_env(global_environment, user, "*user*"); + define_var_in_env(goal_env, user, "*user*"); // setup maps special_forms = { @@ -47,6 +53,7 @@ Interpreter::Interpreter() { {"print", &Interpreter::eval_print}, {"inspect", &Interpreter::eval_inspect}, {"load-file", &Interpreter::eval_load_file}, + {"try-load-file", &Interpreter::eval_try_load_file}, {"eq?", &Interpreter::eval_equals}, {"gensym", &Interpreter::eval_gensym}, {"eval", &Interpreter::eval_eval}, @@ -1030,6 +1037,35 @@ Object Interpreter::eval_load_file(const Object& form, return Object::make_empty_list(); } +/*! + * Combines read-file and eval to load in a file. Return #f if it doesn't exist. + */ +Object Interpreter::eval_try_load_file(const Object& form, + Arguments& args, + const std::shared_ptr& env) { + (void)env; + vararg_check(form, args, {ObjectType::STRING}, {}); + + auto path = {args.unnamed.at(0).as_string()->data}; + if (!std::filesystem::exists(file_util::get_file_path(path))) { + return SymbolObject::make_new(reader.symbolTable, "#f"); + } + + Object o; + try { + o = reader.read_from_file(path); + } catch (std::runtime_error& e) { + throw_eval_error(form, std::string("reader error inside of try-load-file:\n") + e.what()); + } + + try { + return eval_with_rewind(o, global_environment.as_env_ptr()); + } catch (std::runtime_error& e) { + throw_eval_error(form, std::string("eval error inside of try-load-file:\n") + e.what()); + } + return SymbolObject::make_new(reader.symbolTable, "#t"); +} + /*! * Print the form to stdout, including a newline. * Returns () diff --git a/common/goos/Interpreter.h b/common/goos/Interpreter.h index eedbd0a43..7fffb1af6 100644 --- a/common/goos/Interpreter.h +++ b/common/goos/Interpreter.h @@ -13,7 +13,7 @@ namespace goos { class Interpreter { public: - Interpreter(); + Interpreter(const std::string& user_profile = "#f"); ~Interpreter(); void execute_repl(ReplWrapper& repl); void throw_eval_error(const Object& o, const std::string& err); @@ -128,6 +128,9 @@ class Interpreter { Object eval_load_file(const Object& form, Arguments& args, const std::shared_ptr& env); + Object eval_try_load_file(const Object& form, + Arguments& args, + const std::shared_ptr& env); Object eval_print(const Object& form, Arguments& args, const std::shared_ptr& env); diff --git a/common/goos/Reader.cpp b/common/goos/Reader.cpp index ea6b75607..427600c0f 100644 --- a/common/goos/Reader.cpp +++ b/common/goos/Reader.cpp @@ -64,6 +64,7 @@ void TextStream::seek_past_whitespace_and_comments() { case ' ': case '\t': case '\n': + case '\r': // just a whitespace, eat it! read(); break; diff --git a/goal_src/engine/level/level.gc b/goal_src/engine/level/level.gc index 94f51075a..977490e41 100644 --- a/goal_src/engine/level/level.gc +++ b/goal_src/engine/level/level.gc @@ -1455,7 +1455,7 @@ (let ((startup-level (case *kernel-boot-message* (('play) (if *debug-segment* - 'village1 + (#if (user? dass) 'finalboss 'village1) 'title ) ) diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index 9a32cce63..9912fb550 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -109,6 +109,10 @@ `(#cond ((not ,clause) ,@body)) ) +(defmacro #if (clause true false) + `(#cond (,clause ,true) (#t ,false)) + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TARGET CONTROL ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/goal_src/goos-lib.gs b/goal_src/goos-lib.gs index e13622587..ea54b7511 100644 --- a/goal_src/goos-lib.gs +++ b/goal_src/goos-lib.gs @@ -288,7 +288,6 @@ `(seval (desfun ,name ,args ,@body)) ) - ;;;;;;;;;;;;;;;;;;; ;; enum stuff ;;;;;;;;;;;;;;;;;;; @@ -341,3 +340,26 @@ ;; this is checked in a test to see if this file is loaded. (define __goos-lib-loaded__ #t) + + +;;;;;;;;;;;;;;;;;;;;;;;; +;; USER PROFILES ;; +;;;;;;;;;;;;;;;;;;;;;;;; + +;; *user* is defined when goos starts! +(when *user* + (fmt #t "Loading user scripts for user: {}...\n" *user*) + ;; i'm not sure what naming scheme to use here. user//user.gs? + ;; the GOAL one is loaded in Compiler.cpp + (load-file (fmt #f "goal_src/user/{}/user.gs" *user*)) + ) + +(defsmacro user? (&rest users) + (cond + ((null? users) #f) + ((eq? *user* (car users)) #t) + (#t `(user? ,@(cdr users))) + ) + ) + + diff --git a/goal_src/user/.gitignore b/goal_src/user/.gitignore new file mode 100644 index 000000000..5c9682fc4 --- /dev/null +++ b/goal_src/user/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!readme.md \ No newline at end of file diff --git a/goal_src/user/readme.md b/goal_src/user/readme.md new file mode 100644 index 000000000..c015ff463 --- /dev/null +++ b/goal_src/user/readme.md @@ -0,0 +1,14 @@ +This directory holds the user profiles. + +To make your own profile, create a new directory here with your username. +e.g. for username `mark` make a directory called `mark` +Inside that directory, create `user.gs` and `user.gc` files. +These are your own user scripts, loaded after the GOOS library and GOAL library respectively. + +The rest of the directory can be used however you please! + +To automatically log in as a specific user, create a `user.txt` file in this directory +which contains just the username you want to log in as. That way you don't have to +modify multiple scripts when you want to change users. + +If you want to make your profile public, edit the .gitignore in this directory. diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index b7c3ae1c4..894b4d09f 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -11,8 +11,8 @@ using namespace goos; -Compiler::Compiler(std::unique_ptr repl) - : m_debugger(&m_listener, &m_goos.reader), m_repl(std::move(repl)) { +Compiler::Compiler(const std::string& user_profile, std::unique_ptr repl) + : m_goos(user_profile), m_debugger(&m_listener, &m_goos.reader), m_repl(std::move(repl)) { m_listener.add_debugger(&m_debugger); m_ts.add_builtin_types(); m_global_env = std::make_unique(); @@ -25,6 +25,11 @@ Compiler::Compiler(std::unique_ptr repl) Object library_code = m_goos.reader.read_from_file({"goal_src", "goal-lib.gc"}); compile_object_file("goal-lib", library_code, false); + if (user_profile != "#f") { + Object user_code = m_goos.reader.read_from_file({"goal_src", "user", user_profile, "user.gc"}); + compile_object_file(user_profile, user_code, false); + } + // add built-in forms to symbol info for (auto& builtin : g_goal_forms) { m_symbol_info.add_builtin(builtin.first); diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 21fb5fa65..f10ca67b6 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -25,7 +25,7 @@ enum class ReplStatus { OK, WANT_EXIT, WANT_RELOAD }; class Compiler { public: - Compiler(std::unique_ptr repl = nullptr); + Compiler(const std::string& user_profile = "#f", std::unique_ptr repl = nullptr); ReplStatus execute_repl(bool auto_listen = false, bool auto_debug = false); goos::Interpreter& get_goos() { return m_goos; } FileEnv* compile_object_file(const std::string& name, goos::Object code, bool allow_emit); diff --git a/goalc/main.cpp b/goalc/main.cpp index 47f85ba25..a49c1ae52 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -28,21 +28,44 @@ int main(int argc, char** argv) { (void)argv; std::string argument; + std::string username = "#f"; bool verbose = false; bool auto_listen = false; bool auto_debug = false; for (int i = 1; i < argc; i++) { if (std::string("-v") == argv[i]) { verbose = true; - } - if (std::string("-cmd") == argv[i] && i < argc - 1) { + } else if (std::string("-cmd") == argv[i] && i + 1 < argc) { argument = argv[++i]; - } - if (std::string("-auto-lt") == argv[i]) { + } else if (std::string("-auto-lt") == argv[i]) { auto_listen = true; - } - if (std::string("-auto-dbg") == argv[i]) { + } else if (std::string("-auto-dbg") == argv[i]) { auto_debug = true; + } else if (std::string("-user") == argv[i] && i + 1 < argc) { + username = argv[++i]; + } else if (std::string("-user-auto") == argv[i]) { + try { + auto text = std::make_shared( + file_util::get_file_path({"goal_src", "user", "user.txt"}), "goal_src/user/user.txt"); + goos::TextStream ts(text); + ts.seek_past_whitespace_and_comments(); + username.clear(); + while (ts.text_remains()) { + char c = ts.read(); + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + c == '-' || c == '.' || c == '!' || c == '?' || c == '<' || c == '>') { + username.push_back(c); + } else { + break; + } + } + if (username.empty()) { + username = "#f"; + } + } catch (std::exception& e) { + printf("error opening user desc file: %s\n", e.what()); + username = "#f"; + } } } setup_logging(verbose); @@ -56,7 +79,7 @@ int main(int argc, char** argv) { if (argument.empty()) { ReplStatus status = ReplStatus::WANT_RELOAD; while (status == ReplStatus::WANT_RELOAD) { - compiler = std::make_unique(std::make_unique()); + compiler = std::make_unique(username, std::make_unique()); status = compiler->execute_repl(auto_listen, auto_debug); if (status == ReplStatus::WANT_RELOAD) { fmt::print("Reloading compiler...\n"); diff --git a/scripts/batch/gc-no-lt.bat b/scripts/batch/gc-no-lt.bat index 6425289d1..82b4e4d08 100644 --- a/scripts/batch/gc-no-lt.bat +++ b/scripts/batch/gc-no-lt.bat @@ -1,4 +1,4 @@ @echo off cd ..\.. -out\build\Release\bin\goalc -v +out\build\Release\bin\goalc -v -user-auto pause diff --git a/scripts/batch/gc.bat b/scripts/batch/gc.bat index ddcdb5f66..5c69b5e26 100644 --- a/scripts/batch/gc.bat +++ b/scripts/batch/gc.bat @@ -1,4 +1,4 @@ @echo off cd ..\.. -out\build\Release\bin\goalc -v -auto-dbg +out\build\Release\bin\goalc -v -auto-dbg -user-auto pause diff --git a/scripts/batch/repo-settings-mark.bat b/scripts/batch/repo-settings-mark.bat index d44608d55..dd308bc90 100644 --- a/scripts/batch/repo-settings-mark.bat +++ b/scripts/batch/repo-settings-mark.bat @@ -1,2 +1,2 @@ cd ..\.. -git update-index --assume-unchanged decompiler\config\jak1_ntsc_black_label.jsonc decompiler\config\jak1_ntsc_black_label\inputs.jsonc +git update-index --assume-unchanged decompiler\config\jak1_ntsc_black_label.jsonc decompiler\config\jak1_ntsc_black_label\inputs.jsonc goal_src\user\user.txt diff --git a/scripts/batch/repo-settings-unmark.bat b/scripts/batch/repo-settings-unmark.bat index bf3eee789..11e7470db 100644 --- a/scripts/batch/repo-settings-unmark.bat +++ b/scripts/batch/repo-settings-unmark.bat @@ -1,2 +1,2 @@ cd ..\.. -git update-index --no-assume-unchanged decompiler\config\jak1_ntsc_black_label.jsonc decompiler\config\jak1_ntsc_black_label\inputs.jsonc +git update-index --no-assume-unchanged decompiler\config\jak1_ntsc_black_label.jsonc decompiler\config\jak1_ntsc_black_label\inputs.jsonc goal_src\user\user.txt