From fcacc4e0dca6f2c846a03d66c552f702d8ab712c Mon Sep 17 00:00:00 2001
From: Mattias Ajander <mattias@ajander.se>
Date: Mon, 7 Apr 2025 21:58:31 +0200
Subject: [PATCH] Mostly stable REPL implementation with same scope evaluation
 and new exit function

---
 include/ast/BlockNode.h  |  2 ++
 include/parser/BuiltIn.h |  1 +
 include/parser/Parser.h  |  6 +++++
 include/utils/Common.h   |  1 +
 source/ast/BlockNode.cc  |  7 +++++
 source/main.cc           | 58 +++++++++++++++++++++++++++++++++++++---
 source/parser/BuiltIn.cc | 12 +++++++--
 source/parser/Parser.cc  |  9 +++++++
 8 files changed, 91 insertions(+), 5 deletions(-)

diff --git a/include/ast/BlockNode.h b/include/ast/BlockNode.h
index d1ec053..7879f90 100644
--- a/include/ast/BlockNode.h
+++ b/include/ast/BlockNode.h
@@ -17,6 +17,8 @@ public:
     Vector<Node*> get_statements() const;
 
     Node* evaluate() const override;
+    Node* evaluate_same_scope() const;
+
     String to_s() const override;
 
 private:
diff --git a/include/parser/BuiltIn.h b/include/parser/BuiltIn.h
index 130f73e..cd5dbd6 100644
--- a/include/parser/BuiltIn.h
+++ b/include/parser/BuiltIn.h
@@ -16,6 +16,7 @@ class BuiltIn
 public:
     static Node* print(const CallNode& call, const Vector<ExpressionNode*>& args);
     static Node* read(const CallNode& call, const Vector<ExpressionNode*>& args);
+    static Node* fast_exit(const CallNode& call, const Vector<ExpressionNode*>& args);
 
     static HashMap<String, Node* (*)(const CallNode&, const Vector<ExpressionNode*>&)> functions;
 };
diff --git a/include/parser/Parser.h b/include/parser/Parser.h
index d7e1462..bb44e6e 100644
--- a/include/parser/Parser.h
+++ b/include/parser/Parser.h
@@ -47,6 +47,12 @@ public:
      */
     ~Parser();
 
+    /**
+     * @brief Sets the tokens to parse
+     * @param tokens The tokens to parse
+     */
+    void set_tokens(const Vector<Token>& tokens);
+
     /**
      * @brief Parses the token stream and returns the root AST node
      * @param args The arguments passed to the program
diff --git a/include/utils/Common.h b/include/utils/Common.h
index 87fd2f0..2cfde8a 100644
--- a/include/utils/Common.h
+++ b/include/utils/Common.h
@@ -20,6 +20,7 @@
 using std::cerr;
 using std::cin;
 using std::cout;
+using std::endl;
 using std::getline;
 
 namespace funk
diff --git a/source/ast/BlockNode.cc b/source/ast/BlockNode.cc
index 826cdfb..c9892ec 100644
--- a/source/ast/BlockNode.cc
+++ b/source/ast/BlockNode.cc
@@ -30,6 +30,13 @@ Node* BlockNode::evaluate() const
     return result;
 }
 
+Node* BlockNode::evaluate_same_scope() const
+{
+    Node* result{};
+    for (Node* statement : statements) { result = statement->evaluate(); }
+    return result;
+}
+
 String BlockNode::to_s() const
 {
     String repr{};
diff --git a/source/main.cc b/source/main.cc
index 87dc2f0..5457394 100644
--- a/source/main.cc
+++ b/source/main.cc
@@ -47,7 +47,7 @@ bool setup(ArgParser& parser, Config& config)
     // Print help message
     if (parser.has_option("--help"))
     {
-        cout << ArgParser::help("funk [options] <file>", options) << "\n";
+        cout << ArgParser::help("funk [options] <file>", options) << endl;
         return false;
     }
 
@@ -121,10 +121,62 @@ void process_file(const String& file, const Config& config, const Vector<String>
     catch (const FunkError& e)
     {
         LOG_ERROR("Error processing file " + file + ": " + e.what());
-        cerr << "Error: " << e.trace() << '\n';
+        cerr << "Error: " << e.trace() << endl;
     }
 }
 
+/**
+ * @brief Funk REPL
+ * Reads and executes Funk code from the standard input
+ */
+void repl()
+{
+    cout << "Funk REPL. Press Ctrl+D to exit or type 'exit()'." << endl << endl;
+
+    String input;
+    Vector<Token> tokens{};
+    Parser parser{tokens, ""};
+    Scope::instance().push();
+
+    while (true)
+    {
+        cout << ">>> ";
+        cout.flush();
+        if (!getline(cin, input)) { break; }
+        if (input.empty()) { continue; }
+
+        try
+        {
+            // Add a semicolon to the input if it doesn't end with one
+            if (input.back() != ';') { input += ";"; }
+
+            Lexer lexer{input + "\n", ""};
+            Vector<Token> tokens{lexer.tokenize()};
+
+            parser.set_tokens(tokens);
+            BlockNode* ast{static_cast<BlockNode*>(parser.parse())};
+
+            Node* result{ast->evaluate_same_scope()};
+            if (result) { cout << result->to_s() << endl; }
+        }
+        catch (const FunkError& e)
+        {
+            cerr << "Error: " << e.trace() << endl;
+        }
+        catch (const std::exception& e)
+        {
+            cerr << "Error: " << e.what() << endl;
+        }
+        catch (...)
+        {
+            cerr << "Unknown error occurred" << endl;
+        }
+    }
+
+    Scope::instance().pop();
+    cout << "Bye!" << endl;
+}
+
 /**
  * @brief Main entry point for the Funk interpreter
  * Parses command line arguments and processes input files
@@ -142,7 +194,7 @@ int main(int argc, char* argv[])
 
     // Process each file
     if (parser.has_file()) { process_file(parser.get_file(), config, parser.get_args()); }
-    else { cout << "Funk REPL, not implemented yet\n"; }
+    else { repl(); }
 
     return 0;
 }
diff --git a/source/parser/BuiltIn.cc b/source/parser/BuiltIn.cc
index 005e8d7..6606713 100644
--- a/source/parser/BuiltIn.cc
+++ b/source/parser/BuiltIn.cc
@@ -11,7 +11,7 @@ Node* BuiltIn::print(const CallNode& call, const Vector<ExpressionNode*>& args)
         if (!result) { throw RuntimeError(arg->get_location(), "Print argument did not evaluate to an expression"); }
         cout << result->get_value().cast<String>() << " ";
     }
-    cout << "\n";
+    cout << endl;
     return new LiteralNode(call.get_location(), None{});
 }
 
@@ -23,7 +23,15 @@ Node* BuiltIn::read(const CallNode& call, const Vector<ExpressionNode*>& args)
     return new LiteralNode(call.get_location(), input);
 }
 
+Node* BuiltIn::fast_exit(const CallNode& call [[maybe_unused]], const Vector<ExpressionNode*>& args)
+{
+    int status{0};
+    if (!args.empty()) { status = dynamic_cast<LiteralNode*>(args[0]->evaluate())->get_value().cast<int>(); }
+
+    exit(status);
+}
+
 HashMap<String, Node* (*)(const CallNode&, const Vector<ExpressionNode*>&)> BuiltIn::functions{
-    {"print", print}, {"read", read}};
+    {"print", print}, {"read", read}, {"exit", fast_exit}};
 
 } // namespace funk
diff --git a/source/parser/Parser.cc b/source/parser/Parser.cc
index d35ab70..bcaca9f 100644
--- a/source/parser/Parser.cc
+++ b/source/parser/Parser.cc
@@ -24,6 +24,12 @@ Node* Parser::parse(const Vector<String>& args)
     return block;
 }
 
+void Parser::set_tokens(const Vector<Token>& tokens)
+{
+    this->tokens = tokens;
+    this->index = 0;
+}
+
 Parser Parser::load(String filename)
 {
     Lexer lexer{read_file(filename), filename};
@@ -72,6 +78,9 @@ Node* Parser::parse_statement()
 {
     LOG_DEBUG("Parse statement");
 
+    // Empty statement, just a semicolon
+    if (match(TokenType::SEMICOLON)) { return new LiteralNode(peek_prev().get_location(), NodeValue(None())); }
+
     Node* control{parse_control()};
     if (control) { return control; }
 
-- 
GitLab