Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ add_executable(python_interpreter src/main.cpp
src/interpreter/Node/IfNode.h
src/interpreter/Node/ForNode.h
src/interpreter/Node/RangeNode.h
src/interpreter/Node/FunctionDefNode.h
src/interpreter/Node/ReturnNode.h
src/interpreter/Node/CallNode.h
src/interpreter/Parser.cpp
src/interpreter/Parser.h
src/interpreter/Value.h
Expand Down
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,33 @@ cd build
python_interpreter.exe path/to/script.py
```

Supported language features
----------------------------
- Variables, numbers, strings and arithmetic/comparison expressions (`+ - * / > < ==`, unary `-`)
- `print(...)` with multiple comma-separated arguments
- `if` / `else`
- `for x in range(...)`, with the same signatures as Python:
- `range(end)`
- `range(start, end)`
- `range(start, end, step)` (including negative steps, e.g. counting down)
- Functions with `def`, parameters and `return` (including recursion). Functions have their own local scope: assignments inside a function do not leak to the caller.

Example script (example.py)
---------------------------
```
```python
# example.py
x = 0
for i in range(5):
print(i)

print("End of loop")

def factorial(n):
if n < 2:
return 1
return n * factorial(n - 1)

print(factorial(5))
```

Expected output (implementation-dependent):
Expand All @@ -60,12 +78,13 @@ Expected output (implementation-dependent):
3
4
End of loop
120
```

Code structure
--------------
- `interpreter/Lexer.*` - tokenization
- `interpreter/Parser.*` - AST construction
- `interpreter/Interpreter.*` - AST execution/evaluation
- `interpreter/Node/` - AST node definitions (AssignNode, ForNode, PrintNode, etc.)
- `interpreter/Node/` - AST node definitions (AssignNode, ForNode, PrintNode, FunctionDefNode, ReturnNode, CallNode, etc.)
- `interpreter/File.*` - file reading utilities
65 changes: 65 additions & 0 deletions src/interpreter/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,22 @@ along with this program; if not, see
#include "Node/IfNode.h"
#include "Node/ForNode.h"
#include "Node/RangeNode.h"
#include "Node/FunctionDefNode.h"
#include "Node/ReturnNode.h"
#include "Node/CallNode.h"

#include <stdexcept>

namespace {
class ReturnSignal : public std::exception {
public:
Value value;

explicit ReturnSignal(Value v) : value(std::move(v)) {
}
};
}

Value Interpreter::executeNode(std::shared_ptr<ASTNode> node) {
if (auto num = dynamic_pointer_cast<NumberNode>(node)) {
return Value(num->value);
Expand Down Expand Up @@ -134,6 +147,58 @@ Value Interpreter::executeNode(std::shared_ptr<ASTNode> node) {
return Value(0.0);
}

if (auto funcDef = dynamic_pointer_cast<FunctionDefNode>(node)) {
functions[funcDef->name] = funcDef;
return Value(0.0);
}

if (auto ret = dynamic_pointer_cast<ReturnNode>(node)) {
Value val = ret->value ? executeNode(ret->value) : Value(0.0);
throw ReturnSignal(val);
}

if (auto call = dynamic_pointer_cast<CallNode>(node)) {
auto it = functions.find(call->name);
if (it == functions.end()) {
throw std::runtime_error("Function '" + call->name + "' not defined");
}
auto funcDef = dynamic_pointer_cast<FunctionDefNode>(it->second);

if (call->arguments.size() != funcDef->params.size()) {
throw std::runtime_error("Function '" + call->name + "' expects " +
std::to_string(funcDef->params.size()) + " argument(s), got " +
std::to_string(call->arguments.size()));
}

std::vector<Value> argValues;
for (const auto &arg: call->arguments) {
argValues.push_back(executeNode(arg));
}

std::map<std::string, Value> callerVariables = variables;
std::map<std::string, Value> localVariables = variables;
for (size_t i = 0; i < funcDef->params.size(); i++) {
localVariables[funcDef->params[i]] = argValues[i];
}
variables = localVariables;

Value result(0.0);
try {
for (const auto &stmt: funcDef->body)
executeNode(stmt);
} catch (ReturnSignal &signal) {
result = signal.value;
variables = callerVariables;
return result;
} catch (...) {
variables = callerVariables;
throw;
}

variables = callerVariables;
return result;
}

throw std::runtime_error("Unknown node type");
}

Expand Down
1 change: 1 addition & 0 deletions src/interpreter/Interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ along with this program; if not, see
class Interpreter {
private:
std::map<std::string, Value> variables;
std::map<std::string, std::shared_ptr<ASTNode> > functions;
std::vector<std::string> output;

Value executeNode(std::shared_ptr<ASTNode> node);
Expand Down
35 changes: 35 additions & 0 deletions src/interpreter/Node/CallNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright (C) 2025 M3t4l

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, see
<https://www.gnu.org/licenses/>.
*/


#ifndef CALLNODE_H
#define CALLNODE_H


class CallNode : public ASTNode {
public:
std::string name;
std::vector<std::shared_ptr<ASTNode> > arguments;

CallNode(const std::string &n, const std::vector<std::shared_ptr<ASTNode> > &args)
: name(n), arguments(args) {
}
};


#endif //CALLNODE_H
37 changes: 37 additions & 0 deletions src/interpreter/Node/FunctionDefNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright (C) 2025 M3t4l

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, see
<https://www.gnu.org/licenses/>.
*/


#ifndef FUNCTIONDEFNODE_H
#define FUNCTIONDEFNODE_H


class FunctionDefNode : public ASTNode {
public:
std::string name;
std::vector<std::string> params;
std::vector<std::shared_ptr<ASTNode> > body;

FunctionDefNode(const std::string &n, const std::vector<std::string> &p,
const std::vector<std::shared_ptr<ASTNode> > &b)
: name(n), params(p), body(b) {
}
};


#endif //FUNCTIONDEFNODE_H
33 changes: 33 additions & 0 deletions src/interpreter/Node/ReturnNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright (C) 2025 M3t4l

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, see
<https://www.gnu.org/licenses/>.
*/


#ifndef RETURNNODE_H
#define RETURNNODE_H


class ReturnNode : public ASTNode {
public:
std::shared_ptr<ASTNode> value;

ReturnNode(std::shared_ptr<ASTNode> v) : value(v) {
}
};


#endif //RETURNNODE_H
77 changes: 77 additions & 0 deletions src/interpreter/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ along with this program; if not, see
#include "Parser.h"
#include "Node/AssignNode.h"
#include "Node/BinaryOpNode.h"
#include "Node/CallNode.h"
#include "Node/ForNode.h"
#include "Node/FunctionDefNode.h"
#include "Node/IfNode.h"
#include "Node/NumberNode.h"
#include "Node/PrintNode.h"
#include "Node/RangeNode.h"
#include "Node/ReturnNode.h"
#include "Node/StringNode.h"
#include "Node/VariableNode.h"

Expand Down Expand Up @@ -75,6 +78,11 @@ std::shared_ptr<ASTNode> Parser::parsePrimary() {
if (currentToken().type == TokenType::IDENTIFIER) {
std::string name = currentToken().value;
advance();

if (currentToken().type == TokenType::LPAREN) {
return parseCall(name);
}

return std::make_shared<VariableNode>(name);
}

Expand Down Expand Up @@ -237,11 +245,76 @@ std::shared_ptr<ASTNode> Parser::parseFor() {
return make_shared<ForNode>(varName, iterable, body);
}

std::shared_ptr<ASTNode> Parser::parseCall(const std::string &name) {
expect(TokenType::LPAREN);

std::vector<std::shared_ptr<ASTNode> > args;
if (currentToken().type != TokenType::RPAREN) {
args.push_back(parseExpression());
while (currentToken().type == TokenType::COMMA) {
advance();
args.push_back(parseExpression());
}
}

expect(TokenType::RPAREN);
return make_shared<CallNode>(name, args);
}

std::shared_ptr<ASTNode> Parser::parseDef() {
expect(TokenType::KEYWORD);
std::string name = currentToken().value;
expect(TokenType::IDENTIFIER);
expect(TokenType::LPAREN);

std::vector<std::string> params;
if (currentToken().type != TokenType::RPAREN) {
params.push_back(currentToken().value);
expect(TokenType::IDENTIFIER);
while (currentToken().type == TokenType::COMMA) {
advance();
params.push_back(currentToken().value);
expect(TokenType::IDENTIFIER);
}
}

expect(TokenType::RPAREN);
expect(TokenType::COLON);
expect(TokenType::NEWLINE);
expect(TokenType::INDENT);

std::vector<std::shared_ptr<ASTNode> > body;
while (currentToken().type != TokenType::DEDENT && currentToken().type != TokenType::END_OF_FILE) {
body.push_back(parseStatement());
skipNewlines();
}

if (currentToken().type == TokenType::DEDENT)
advance();

return make_shared<FunctionDefNode>(name, params, body);
}

std::shared_ptr<ASTNode> Parser::parseReturn() {
expect(TokenType::KEYWORD);

std::shared_ptr<ASTNode> value = nullptr;
if (currentToken().type != TokenType::NEWLINE &&
currentToken().type != TokenType::DEDENT &&
currentToken().type != TokenType::END_OF_FILE) {
value = parseExpression();
}

return make_shared<ReturnNode>(value);
}

std::shared_ptr<ASTNode> Parser::parseStatement() {
if (currentToken().type == TokenType::KEYWORD) {
if (currentToken().value == "print") return parsePrint();
if (currentToken().value == "if") return parseIf();
if (currentToken().value == "for") return parseFor();
if (currentToken().value == "def") return parseDef();
if (currentToken().value == "return") return parseReturn();
}

if (currentToken().type == TokenType::IDENTIFIER) {
Expand All @@ -253,6 +326,10 @@ std::shared_ptr<ASTNode> Parser::parseStatement() {
auto value = parseExpression();
return make_shared<AssignNode>(varName, value);
}

if (currentToken().type == TokenType::LPAREN) {
return parseCall(varName);
}
}

throw std::runtime_error("Invalid statement on line " + std::to_string(currentToken().line));
Expand Down
6 changes: 6 additions & 0 deletions src/interpreter/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ class Parser {

std::shared_ptr<ASTNode> parseFor();

std::shared_ptr<ASTNode> parseDef();

std::shared_ptr<ASTNode> parseReturn();

std::shared_ptr<ASTNode> parseCall(const std::string &name);

std::shared_ptr<ASTNode> parseStatement();

public:
Expand Down
Loading