Skip to content
Draft
87 changes: 56 additions & 31 deletions src/environment/Scope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,71 +8,96 @@
*/

#include "Scope.hpp"
#include "AnyObject.hpp"
#include "Exceptions.hpp"
#include "Logger.hpp"
#include <cassert>
#include <functional>
#include <iostream>

Scope::Scope(const Scope &_parent)
: Scope(&_parent)
{
}

Scope::Scope(const Scope *_parent)
: parent(const_cast<Scope *>(_parent))
Scope::Scope(const Scope &parentScope)
: _enclosingScope(const_cast<Scope *>(&parentScope))
{
}


BaseObject::Ptr Scope::getOptionalNamedObject(const std::string &name) const
AnyObject *Scope::getObjectPtr(const VariableName &name) const
{
// Try in our scope (to handle variable shadowing).
auto iter = linkedObjectForName.find(name);
if (iter != linkedObjectForName.end())
auto iter = _objectPtrMap.find(name);
if (iter != _objectPtrMap.end())
{
return (iter->second);
return const_cast<AnyObject *>(iter->second); // TODO: - bit dodgy with const_cast
}

// Otherwise check if it is defined in our parent's scope? Keep working outwards.
if (parent)
if (_enclosingScope)
{
return parent->getOptionalNamedObject(name);
return _enclosingScope->getObjectPtr(name);
}

// Not defined.
return nullptr;
}

#include "Logger.hpp"

BaseObject::Ptr Scope::getNamedObject(const std::string &name) const
AnyObject &Scope::getObjectRef(const VariableName &name) const
{
BaseObject::Ptr obj = getOptionalNamedObject(name);
if (!obj)
log().info("Getting object reference for name " + name);
auto ptr = getObjectPtr(name);

if (ptr)
{
ThrowException("undefined variable " + name);
log().debug("the pointer type is: <" + ptr->typeToString() + ">");

return *ptr;
}

return obj;
// Not defined.
ThrowException("No variable defined in scope with name [" + name + "]");
}


bool Scope::hasNamedObject(const std::string &name) const
void Scope::checkForNameClashesInCurrentScope(const VariableName &name) const
{
return (getOptionalNamedObject(name) != nullptr);
if (!_objectPtrMap.count(name))
return;

ThrowException("Variable [" + name + "] is already defined in current scope");
}


void Scope::linkObject(const std::string &name, BaseObject::Ptr object)
AnyObject::Ref Scope::alias(const VariableName &nameAlias, const VariableName &name)
{
assert(object != nullptr);
checkForNameClashesInCurrentScope(nameAlias);

// 1. Check for name clashes. This is where we have two variables with
// the same name defined in the SAME scope.
auto iter = linkedObjectForName.find(name);
if (iter != linkedObjectForName.end())
auto *object = getObjectPtr(name);
if (!object)
{
ThrowException(name + " is already defined in current scope");
ThrowException("No variable defined in scope with name [" + name + "]");
}

// 2. Add to map. This will ensure that we now ignore any outer-scope variables
// with this name (variable shadowing).
linkedObjectForName[name] = object;
_objectPtrMap[nameAlias] = object;

return std::ref(*object);
}


AnyObject::Ref Scope::link(const VariableName &name, AnyObject &&object)
{
log().debug("Adding variable '" + name + "' with type '" + object.typeToString() + "'");

assert(!name.empty() && object.getType() != AnyObject::NotSet);

// 1. Check for name clashes. This is where we have two variables with the same name defined in the SAME scope.
// Note that it's okay to have the same variable defined multiple times if they're in different scopes --
// this is 'variable shadowing'.
checkForNameClashesInCurrentScope(name);

// 2. Add to map. This will ensure that we now ignore any outer-scope variables with this name (variable shadowing).
_objects.push_back(object);

_objectPtrMap[name] = &_objects.back();

return _objects.back();
}
66 changes: 24 additions & 42 deletions src/environment/Scope.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,47 @@
*/

#pragma once
#include "BaseObject.hpp"
#include <new>
#include <string>
#include <unordered_map>
#include <vector>
#include <list>

class Scope
{
public:
Scope(const Scope &_parent);
Scope(const Scope *_parent = nullptr);
~Scope() = default;
using VariableName = std::string;

/// Returns true if named object ("variable") is defined in our scope or in
/// a parent scope.
bool hasNamedObject(const std::string &name) const;
Scope() = default;
Scope(const Scope &parentScope);

/// Get a named object ("variable") in our scope or an outer scope. We work
/// outwards from our scope to handle variable shadowing correctly. If the
/// object is not found, return nullptr.
BaseObject::Ptr getOptionalNamedObject(const std::string &name) const;
/// Get a named object ("variable") in our scope or an outer scope. We work outwards from our scope to handle variable shadowing correctly.
class AnyObject &getObjectRef(const VariableName &name) const;

/// Similar to getOptionalObject but has a check to ensure pointer is valid.
BaseObject::Ptr getNamedObject(const std::string &name) const;
class AnyObject *getObjectPtr(const VariableName &name) const;

/// Get an object from the scope and cast to a subclass.
template <typename TObject>
std::shared_ptr<TObject> getNamedObject(const std::string &name) const
{
auto objectPtr = getNamedObject(name);
return std::static_pointer_cast<TObject>(objectPtr);
}

template <typename TObject>
std::shared_ptr<TObject> getOptionalNamedObject(const std::string &name) const
{
BaseObject::Ptr obj = getOptionalNamedObject(name);
if (!obj)
{
return nullptr;
}
/// Create a link between a variable name and an object in this scope.
class AnyObject &link(const VariableName &name, AnyObject &&object);

return std::static_pointer_cast<TObject>(obj);
}
/// Add a link between an already-defined object in this scope and another name to reference it.
class AnyObject &alias(const VariableName &nameAlias, const VariableName &name);

/// Returns non-const reference to parent scope.
inline Scope *parentScope() { return parent; }
inline Scope *parentScope() { return _enclosingScope; }

/// Set a new parent scope. Use with care!
void setParentScope(Scope *parent_) { parent = parent_; }
void setParentScope(Scope *parent) { _enclosingScope = parent; }

/// Create a link between a variable name and an object in this scope.
void linkObject(const std::string &name, BaseObject::Ptr object);
protected:
/// Throws if the name is already defined in this scope.
void checkForNameClashesInCurrentScope(const VariableName &name) const;

private:
/// Stores a mapping from the variable name to a pointer to the object. These
/// are only linked objects defined in this scope. This enables variable
/// shadowing.
std::unordered_map<std::string, BaseObject::Ptr> linkedObjectForName;
using AnyObjectPtrMap = std::unordered_map<VariableName, class AnyObject *>;

/* Stores references (mapped to _linkedObjects vector) */
AnyObjectPtrMap _objectPtrMap;

/* Stores all objects added to scope */
std::list<class AnyObject> _objects;

Scope *parent{nullptr};
Scope *_enclosingScope{nullptr};
};
2 changes: 1 addition & 1 deletion src/eucleia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ int main(int argc, const char *argv[])
CLIParser parser("eucleia");

parser.addFlagArg("--help", "display available options");
parser.addFlagArg("--trace", "logs everything!");
parser.addFlagArg("--trace", "logs everything!"); /* TODO: - enable user to set different log levels or disable */

parser.addPositionalArg("fileName");
parser.parseArgs(argc, argv);
Expand Down
3 changes: 1 addition & 2 deletions src/interpreter/EucleiaInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
//

#include "EucleiaInterpreter.hpp"
#include "BaseObject.hpp"

#include "FileParser.hpp"
#include "Objects.hpp"
#include "Scope.hpp"
#include <iostream>

Expand Down
3 changes: 2 additions & 1 deletion src/lexer/CharStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "Stringify.hpp"
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <stdio.h>
#include <stdlib.h>

Expand Down Expand Up @@ -172,5 +173,5 @@ unsigned int CharStream::endCol(unsigned int lineNum) const

std::string CharStream::location() const
{
return eucleia::stringify("File \"%s\", Ln %d, Col %d", path.c_str(), line, col);
return eucleia::stringify("(%s:%d:%d)", path.filename().c_str(), line, col);
}
4 changes: 2 additions & 2 deletions src/lexer/CharStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

#pragma once
#include <filesystem>
#include <string>
#include <unordered_map>

Expand Down Expand Up @@ -59,8 +60,7 @@ class CharStream
private:
unsigned int endCol(unsigned int lineNum) const;


const std::string path;
const std::filesystem::path path;

char *base{nullptr};
char *ptr{nullptr};
Expand Down
38 changes: 38 additions & 0 deletions src/lexer/Token.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @file Token.cpp
* @author Edward Palmer
* @date 2025-05-24
*
* @copyright Copyright (c) 2025
*
*/

#include "Token.hpp"


std::string Token::typeToString() const /* TODO: - more efficient to have a static maps and return a reference to string */
{
switch (_type)
{
case NotSet:
return "NotSet";
case EndOfFile:
return "EndOfFile";
case Punctuation:
return "Punctuation";
case Keyword:
return "Keyword";
case Variable:
return "Variable";
case String:
return "String";
case Operator:
return "Operator";
case Int:
return "Int";
case Float:
return "Float";
default:
ThrowException("Unknown token type");
}
}
4 changes: 3 additions & 1 deletion src/lexer/Token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class Token : public std::string
Float
};

std::string typeToString() const;

/* Constructors */
Token(Type type) : _type(type) {}
Token(std::string &value, Type type = NotSet) : std::string(value), _type(type) {}
Expand Down Expand Up @@ -115,4 +117,4 @@ Token Tokens::dequeue()
pop();

return next;
}
}
2 changes: 1 addition & 1 deletion src/lexer/Tokenizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Tokens Tokenizer::buildTokens(const std::string &path)
while (!stream.isLast())
{
Token token = buildNextToken(stream);
log().debug(stream.location() + ": " + token);
log().debug("Parsed '" + token + "' => " + token.typeToString() + " " + stream.location());

if (token.type() != Token::EndOfFile)
tokens.push(std::move(token));
Expand Down
Loading