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