Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions clox/Env.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <unordered_map>
#include <memory>
#include <string>
#include <any>
#include <stdexcept>
#include "Token.h"

class Environment
{
private:
std::unordered_map<std::string, std::any> 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 + "'.");
}
};
46 changes: 45 additions & 1 deletion clox/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,36 @@
#include <typeinfo>
#include <iostream>


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<<stringify(value)<<std::endl;
return nullptr;

}

std::any Interpreter::visitLiteralExpr(Literal* expr) {
std::cout << "Evaluating literal: " << stringify(expr->value) << std::endl;
return expr->value;
Expand Down Expand Up @@ -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<std::shared_ptr<Stmt>>& 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);
Expand Down Expand Up @@ -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<bool>(val);
Expand Down
21 changes: 16 additions & 5 deletions clox/Interpreter.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#pragma once

#include "exp.h"
#include "Stmt.h"
#include <any>
#include <string>
#include <stdexcept>

#include <vector>
#include "Env.h"
#include "Token.h"
// Custom exception for runtime errors with token info
class RuntimeError : public std::runtime_error {
public:
Expand All @@ -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;
Expand All @@ -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<std::shared_ptr<Stmt>>& statements);
std::any interpret(Expr* expr); // legacy: expression-only
std::string stringify(const std::any& value);

private:
Expand All @@ -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<std::shared_ptr<Stmt>>& statements, std::shared_ptr<Environment> env);

std::shared_ptr<Environment> environment = std::make_shared<Environment>();

};
62 changes: 55 additions & 7 deletions clox/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,68 @@
Parser::Parser(const std::vector<Token>& tokens) : tokens(tokens) {}

// Main parse method - returns expression AST or nullptr on error
std::shared_ptr<Expr> Parser::parse() {
try {
return expression();
} catch (const ParseError& error) {
// Error already reported, return nullptr
return nullptr;
}
std::vector<std::shared_ptr<Stmt>> Parser::parse() {
std::vector<std::shared_ptr<Stmt>> statements;
while(!isAtEnd())
{
statements.push_back(declaration());
}

return statements;
}



// expression -> assignment
std::shared_ptr<Expr> Parser::expression() {
return assignment();
}
std::shared_ptr<Stmt> Parser::statement()
{
if(match(PRINT))
{
return printStatement();
}
return expressionStatement();
}

std::shared_ptr<Stmt> Parser::printStatement()
{
std::shared_ptr<Expr> value=expression();
consume(SEMICOLON,"Expect ';' after value.");
return std::make_shared<Print>(value);
}

std::shared_ptr<Stmt> Parser::expressionStatement()
{
std::shared_ptr<Expr> expr=expression();
consume(SEMICOLON,"Expect ';' after expression.");
return std::make_shared<Expression>(expr);
}

std::shared_ptr<Stmt> Parser::declaration() {
try {
if (match(VAR)) return varDeclaration();
return statement();
} catch (const ParseError& error) {
synchronize();
return nullptr;
}
}

std::shared_ptr<Stmt>Parser::varDeclaration()
{

Token name = consume(IDENTIFIER, "Expect variable name.");
std::shared_ptr<Expr> initializer = nullptr;
if(match(EQUAL))
{
initializer=expression();
}
consume(SEMICOLON, "Expect ';' after variable declaration.");
return std::make_shared<Var>(name, initializer);

}
// assignment -> IDENTIFIER "=" assignment | logicalOr
std::shared_ptr<Expr> Parser::assignment() {
std::shared_ptr<Expr> expr = logicalOr();
Expand Down
11 changes: 10 additions & 1 deletion clox/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <stdexcept>
#include "exp.h"
#include "Token.h"
#include "Stmt.h"

// Custom exception for parse errors
class ParseError : public std::runtime_error {
Expand All @@ -17,6 +18,14 @@ class Parser {
std::vector<Token> tokens;
int current = 0;


std::shared_ptr<Stmt> statement();

// Specific statement parsers
std::shared_ptr<Stmt> printStatement(); // print expr;
std::shared_ptr<Stmt> expressionStatement(); // expr;
std::shared_ptr<Stmt> declaration();
std::shared_ptr<Stmt> varDeclaration();
// Expression parsing methods (precedence climbing)
std::shared_ptr<Expr> expression();
std::shared_ptr<Expr> assignment();
Expand Down Expand Up @@ -47,5 +56,5 @@ class Parser {

public:
Parser(const std::vector<Token>& tokens);
std::shared_ptr<Expr> parse();
std::vector<std::shared_ptr<Stmt>> parse();
};
61 changes: 61 additions & 0 deletions clox/Stmt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

#pragma once

#include <iostream>
#include <memory>
#include <vector>
#include <any>
#include <string>
#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<Expr> expression;
Expression(std::shared_ptr<Expr> expression):expression(expression){}

std::any accept(StmtVisitor&visitor) override {
return visitor.visitExpressionStmt(this);
}

};
class Print : public Stmt {
public:
const std::shared_ptr<Expr> expression;
Print(std::shared_ptr<Expr> expression) : expression(expression) {}
std::any accept(StmtVisitor& visitor) override {
return visitor.visitPrintStmt(this);
}
};

class Var : public Stmt {
public:
const Token name;
std::shared_ptr<Expr>initializer;
Var(Token name, std::shared_ptr<Expr> initializer)
: name(name), initializer(initializer) {}
std::any accept(StmtVisitor& visitor) override {
return visitor.visitVarStmt(this);
}
};
54 changes: 38 additions & 16 deletions clox/interpreter_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Token> tokens = scanner.scanTokens();

Parser parser(tokens);
auto expression = parser.parse();

if (expression == nullptr) {
std::vector<std::shared_ptr<Stmt>> 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<Token> tokens = scanner.scanTokens();

Parser parser(tokens);
std::vector<std::shared_ptr<Stmt>> 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() {
Expand Down Expand Up @@ -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;
Expand Down
Loading