diff --git a/clox/Env.h b/clox/Env.h new file mode 100644 index 0000000..feeef62 --- /dev/null +++ b/clox/Env.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "Token.h" + +class Environment +{ +private: +std::unordered_map values; +public: + + void define(const std::string& name,std::any value) + { + values[name]=value; + } + std::any get(const Token& name) { + // Check current scope + auto it = values.find(name.lexeme); + if (it != values.end()) { + return it->second; + } + throw std::runtime_error("Undefined variable '" + name.lexeme + "'."); + } +}; \ No newline at end of file diff --git a/clox/Interpreter.cpp b/clox/Interpreter.cpp index 7d355b7..2f379f7 100644 --- a/clox/Interpreter.cpp +++ b/clox/Interpreter.cpp @@ -6,6 +6,36 @@ #include #include + +std::any Interpreter::visitVarStmt(Var* stmt) { + + std::any value=nullptr; + if(stmt->initializer!=nullptr) + { + value=evaluate(stmt->initializer.get()); + } + environment->define(stmt->name.lexeme,value); + return std::any(); +}; + + + + std::any Interpreter:: visitExpressionStmt(Expression* stmt) + { + + evaluate(stmt->expression.get()); + return nullptr; + + } + std::any Interpreter:: visitPrintStmt(Print* stmt) + { + + std::any value=evaluate(stmt->expression.get()); + std::cout<value) << std::endl; return expr->value; @@ -108,10 +138,20 @@ std::any Interpreter::visitThisExpr(This* expr) { } std::any Interpreter::visitVariableExpr(Variable* expr) { - throw std::runtime_error("Variable not yet implemented."); + return environment->get(expr->name); } // Public interface + +void Interpreter::interpret(const std::vector>& statements) { + try { + for (const auto& statement : statements) { + execute(statement.get()); + } + } catch (const RuntimeError& error) { + std::cerr << "[line " << error.token.line << "] Runtime error: " << error.what() << std::endl; + } +} std::any Interpreter::interpret(Expr* expr) { try { return evaluate(expr); @@ -148,6 +188,10 @@ std::any Interpreter::evaluate(Expr* expr) { return expr->accept(*this); } +void Interpreter::execute(Stmt* stmt) { + stmt->accept(*this); +} + bool Interpreter::isTruthy(const std::any& val) { if (!val.has_value()) return false; if (val.type() == typeid(bool)) return std::any_cast(val); diff --git a/clox/Interpreter.h b/clox/Interpreter.h index 9396281..8dbd52b 100644 --- a/clox/Interpreter.h +++ b/clox/Interpreter.h @@ -1,10 +1,13 @@ #pragma once #include "exp.h" +#include "Stmt.h" #include #include #include - +#include +#include "Env.h" +#include "Token.h" // Custom exception for runtime errors with token info class RuntimeError : public std::runtime_error { public: @@ -14,7 +17,7 @@ class RuntimeError : public std::runtime_error { : std::runtime_error(message), token(token) {} }; -class Interpreter : public ExprVisitor { +class Interpreter : public ExprVisitor, public StmtVisitor { public: std::any visitLiteralExpr(Literal* expr) override; std::any visitGroupingExpr(Grouping* expr) override; @@ -28,9 +31,12 @@ class Interpreter : public ExprVisitor { std::any visitSuperExpr(Super* expr) override; std::any visitThisExpr(This* expr) override; std::any visitVariableExpr(Variable* expr) override; - - // Public interface to interpret an expression - std::any interpret(Expr* expr); + std::any visitExpressionStmt(Expression* stmt) override; + std::any visitPrintStmt(Print* stmt) override; + std::any visitVarStmt(Var* stmt) override; + // Public interface + void interpret(const std::vector>& statements); + std::any interpret(Expr* expr); // legacy: expression-only std::string stringify(const std::any& value); private: @@ -39,4 +45,9 @@ class Interpreter : public ExprVisitor { bool isEqual(const std::any& a, const std::any& b); void checkNumberOperand(const Token& op, const std::any& operand); void checkNumberOperands(const Token& op, const std::any& left, const std::any& right); + void execute(Stmt* stmt); + void executeBlock(const std::vector>& statements, std::shared_ptr env); + + std::shared_ptr environment = std::make_shared(); + }; diff --git a/clox/Parser.cpp b/clox/Parser.cpp index 759b1a0..b8fe0d1 100644 --- a/clox/Parser.cpp +++ b/clox/Parser.cpp @@ -4,20 +4,68 @@ Parser::Parser(const std::vector& tokens) : tokens(tokens) {} // Main parse method - returns expression AST or nullptr on error -std::shared_ptr Parser::parse() { - try { - return expression(); - } catch (const ParseError& error) { - // Error already reported, return nullptr - return nullptr; - } +std::vector> Parser::parse() { + std::vector> statements; + while(!isAtEnd()) + { + statements.push_back(declaration()); + } + + return statements; } + + // expression -> assignment std::shared_ptr Parser::expression() { return assignment(); } +std::shared_ptr Parser::statement() +{ + if(match(PRINT)) + { + return printStatement(); + } + return expressionStatement(); +} + +std::shared_ptr Parser::printStatement() +{ + std::shared_ptr value=expression(); + consume(SEMICOLON,"Expect ';' after value."); + return std::make_shared(value); +} + +std::shared_ptr Parser::expressionStatement() +{ + std::shared_ptr expr=expression(); + consume(SEMICOLON,"Expect ';' after expression."); + return std::make_shared(expr); +} + +std::shared_ptr Parser::declaration() { + try { + if (match(VAR)) return varDeclaration(); + return statement(); + } catch (const ParseError& error) { + synchronize(); + return nullptr; + } +} +std::shared_ptrParser::varDeclaration() +{ + + Token name = consume(IDENTIFIER, "Expect variable name."); + std::shared_ptr initializer = nullptr; + if(match(EQUAL)) + { + initializer=expression(); + } + consume(SEMICOLON, "Expect ';' after variable declaration."); + return std::make_shared(name, initializer); + +} // assignment -> IDENTIFIER "=" assignment | logicalOr std::shared_ptr Parser::assignment() { std::shared_ptr expr = logicalOr(); diff --git a/clox/Parser.h b/clox/Parser.h index ba3e673..8f2cefa 100644 --- a/clox/Parser.h +++ b/clox/Parser.h @@ -5,6 +5,7 @@ #include #include "exp.h" #include "Token.h" +#include "Stmt.h" // Custom exception for parse errors class ParseError : public std::runtime_error { @@ -17,6 +18,14 @@ class Parser { std::vector tokens; int current = 0; + + std::shared_ptr statement(); + + // Specific statement parsers + std::shared_ptr printStatement(); // print expr; + std::shared_ptr expressionStatement(); // expr; + std::shared_ptr declaration(); + std::shared_ptr varDeclaration(); // Expression parsing methods (precedence climbing) std::shared_ptr expression(); std::shared_ptr assignment(); @@ -47,5 +56,5 @@ class Parser { public: Parser(const std::vector& tokens); - std::shared_ptr parse(); + std::vector> parse(); }; diff --git a/clox/Stmt.h b/clox/Stmt.h new file mode 100644 index 0000000..c908d3a --- /dev/null +++ b/clox/Stmt.h @@ -0,0 +1,61 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include "Token.h" +#include "exp.h" + + +// Forward declarations so StmtVisitor can reference these classes +class Expression; +class Print; +class Var; + +class StmtVisitor{ +public: +virtual std::any visitExpressionStmt(Expression* stmt)=0; +virtual std::any visitPrintStmt(Print* stmt)=0; +virtual std::any visitVarStmt(Var* stmt)=0; +virtual ~StmtVisitor() = default; +}; + + +class Stmt { +public: + virtual ~Stmt() = default; + virtual std::any accept(StmtVisitor& visitor) = 0; +}; + +class Expression : public Stmt { +public: +const std::shared_ptr expression; +Expression(std::shared_ptr expression):expression(expression){} + +std::any accept(StmtVisitor&visitor) override { + return visitor.visitExpressionStmt(this); +} + +}; +class Print : public Stmt { +public: + const std::shared_ptr expression; + Print(std::shared_ptr expression) : expression(expression) {} + std::any accept(StmtVisitor& visitor) override { + return visitor.visitPrintStmt(this); + } +}; + +class Var : public Stmt { +public: + const Token name; + std::shared_ptrinitializer; + Var(Token name, std::shared_ptr initializer) + : name(name), initializer(initializer) {} + std::any accept(StmtVisitor& visitor) override { + return visitor.visitVarStmt(this); + } +}; \ No newline at end of file diff --git a/clox/interpreter_test.cpp b/clox/interpreter_test.cpp index 470cf8b..a5ace1c 100644 --- a/clox/interpreter_test.cpp +++ b/clox/interpreter_test.cpp @@ -57,31 +57,40 @@ class AstPrinter : public ExprVisitor { } }; -void runTest(const std::string& source, const std::string& description) { +// Run statement-based test (source must end with ';') +void runStmtTest(const std::string& source, const std::string& description) { std::cout << "\n=== " << description << " ===" << std::endl; std::cout << "Source: " << source << std::endl; - + Scanner scanner(source); std::vector tokens = scanner.scanTokens(); - + Parser parser(tokens); - auto expression = parser.parse(); - - if (expression == nullptr) { + std::vector> statements = parser.parse(); + + Interpreter interpreter; + interpreter.interpret(statements); +} + +// Legacy: expression-only test (no semicolon needed) +void runTest(const std::string& source, const std::string& description) { + std::cout << "\n=== " << description << " ===" << std::endl; + std::cout << "Source: " << source << std::endl; + + Scanner scanner(source + ";"); // wrap as expression statement + std::vector tokens = scanner.scanTokens(); + + Parser parser(tokens); + std::vector> statements = parser.parse(); + + if (statements.empty() || statements[0] == nullptr) { std::cout << "Parse error occurred." << std::endl; return; } - - // Print the AST structure - AstPrinter printer; - std::cout << "AST: " << printer.print(expression.get()) << std::endl; - + + // Extract the expression from the ExpressionStmt for legacy display Interpreter interpreter; - std::any result = interpreter.interpret(expression.get()); - - if (result.has_value()) { - std::cout << "Result: " << interpreter.stringify(result) << std::endl; - } + interpreter.interpret(statements); } int main() { @@ -137,6 +146,19 @@ int main() { runTest("!(5 > 3)", "Negation of comparison"); runTest("(10 + 5) / 3 > 4", "Comparison with arithmetic"); +// Statement tests + runStmtTest("print 1 + 2;", "Print statement: 1 + 2"); + runStmtTest("print \"hello\";", "Print string"); + runStmtTest("print true;", "Print boolean"); + runStmtTest("1 + 2;", "Expression statement (no output)"); + + // Variable tests + runStmtTest("var x = 10; print x;", "Declare and print variable"); + runStmtTest("var a = 1; var b = 2; print a + b;", "Two variables"); + runStmtTest("var x = 5; print x * 2;", "Variable in expression"); + runStmtTest("var name = \"Lox\"; print name;", "String variable"); + runStmtTest("var x; print x;", "Uninitialized variable (nil)"); + std::cout << "\n=== All tests completed ===" << std::endl; return 0; diff --git a/clox/parser_test.cpp b/clox/parser_test.cpp index 1fffa9f..78e16c0 100644 --- a/clox/parser_test.cpp +++ b/clox/parser_test.cpp @@ -98,11 +98,16 @@ void testParser(const std::string& source) { std::vector tokens = scanner.scanTokens(); Parser parser(tokens); - std::shared_ptr expression = parser.parse(); + auto statements = parser.parse(); - if (expression) { + if (!statements.empty() && statements[0] != nullptr) { + // Extract expression from ExpressionStmt for display AstPrinter printer; - std::cout << "AST: " << printer.print(expression.get()) << std::endl; + if (auto exprStmt = dynamic_cast(statements[0].get())) { + std::cout << "AST: " << printer.print(exprStmt->expression.get()) << std::endl; + } else { + std::cout << "Parsed as statement." << std::endl; + } } else { std::cout << "Parse failed." << std::endl; }