diff --git a/BarkScript.cpp b/BarkScript.cpp index 7400b35..e98f3b2 100644 --- a/BarkScript.cpp +++ b/BarkScript.cpp @@ -8,10 +8,12 @@ #include "interpreter/interpreter.h" #include "context/context.h" -const std::string bsversion = "0.0.12"; +const std::string bsversion = "0.1.0"; int main() { std::cout << "BarkScript version " << bsversion << std::endl; + spContext context = std::make_shared(Context("
")); + context->symbolTable = std::make_shared(SymbolTable()); while (true) { //std::string input = "5+55"; std::string input; @@ -42,7 +44,6 @@ int main() { std::cout << std::endl; Interpreter interpreter; - spContext context = std::make_shared(Context("
")); RuntimeResult rt = interpreter.visit(abSyTree.node, context); if (rt.hasError()) { std::cout << rt.error->to_string() << std::endl; diff --git a/BarkScript.vcxproj b/BarkScript.vcxproj index 01e7a4f..7fdab89 100644 --- a/BarkScript.vcxproj +++ b/BarkScript.vcxproj @@ -144,6 +144,7 @@ + @@ -154,7 +155,9 @@ + + diff --git a/BarkScript.vcxproj.filters b/BarkScript.vcxproj.filters index 8b4c753..2fa1595 100644 --- a/BarkScript.vcxproj.filters +++ b/BarkScript.vcxproj.filters @@ -30,6 +30,9 @@ Source Files + + Source Files + @@ -66,5 +69,11 @@ Header Files + + Header Files + + + Header Files + \ No newline at end of file diff --git a/Makefile b/Makefile index e08a85e..dcc9644 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -windowsvs : build BarkScript.cpp lexer/Lexer.cpp parser/Parser.cpp object/Object.cpp interpreter/Interpreter.cpp +windowsvs : build BarkScript.cpp lexer/Lexer.cpp parser/Parser.cpp object/Object.cpp interpreter/Interpreter.cpp symboltable/SymbolTable.cpp .\build.bat -linuxgpp : build BarkScript.cpp lexer/Lexer.cpp parser/Parser.cpp object/Object.cpp interpreter/Interpreter.cpp - g++ -o ./build/BarkScript -O2 -Wall BarkScript.cpp lexer/Lexer.cpp parser/Parser.cpp object/Object.cpp interpreter/Interpreter.cpp +linuxgpp : build BarkScript.cpp lexer/Lexer.cpp parser/Parser.cpp object/Object.cpp interpreter/Interpreter.cpp symboltable/SymbolTable.cpp + g++ -o ./build/BarkScript -O2 -Wall BarkScript.cpp lexer/Lexer.cpp parser/Parser.cpp object/Object.cpp interpreter/Interpreter.cpp symboltable/SymbolTable.cpp cleanobj : rm *.obj diff --git a/ast/ast.h b/ast/ast.h index 229f444..131a77c 100644 --- a/ast/ast.h +++ b/ast/ast.h @@ -5,11 +5,14 @@ #include #include "../token/token.h" #include "../position/position.h" +#include "../symboltable/symboltable.h" namespace nodetypes { using namespace std; const string Number = "NUMBER"; + const string VariableAssignment = "VARASS"; + const string VariableRetrievement = "VARRET"; const string BinaryOperator = "BINOP"; const string UnaryOperator = "UNOP"; const string Error = "ERROR"; @@ -41,6 +44,7 @@ struct Node { spNode leftNode; spNode rightNode; + spNode valueNode; }; struct NumberNode : Node { @@ -56,6 +60,33 @@ struct NumberNode : Node { } }; +struct VariableAssignmentNode : Node { + VariableAssignmentNode(Token token, spNode valueNode) { + this->nodeType = nodetypes::VariableAssignment; + this->token = token; + this->valueNode = valueNode; + this->positionStart = token.positionStart; + this->positionEnd = valueNode->positionEnd; + } + + std::string to_string() override { + return token.value + " = " + valueNode->to_string(); + } +}; + +struct VariableRetrievementNode : Node { + VariableRetrievementNode(Token token) { + this->nodeType = nodetypes::VariableRetrievement; + this->token = token; + this->positionStart = token.positionStart; + this->positionEnd = token.positionEnd; + } + + std::string to_string() override { + return token.value; + } +}; + struct BinaryOperatorNode : Node { BinaryOperatorNode(spNode leftNode, Token token, spNode rightNode) { this->nodeType = nodetypes::BinaryOperator; diff --git a/build.bat b/build.bat index 85fbbd2..1fb79a6 100644 --- a/build.bat +++ b/build.bat @@ -1 +1 @@ -"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat" && cl.exe /EHsc BarkScript.cpp lexer/Lexer.cpp parser/Parser.cpp object/Object.cpp interpreter/Interpreter.cpp /link /out:build/BarkScript.exe +"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat" && cl.exe /EHsc BarkScript.cpp lexer/Lexer.cpp parser/Parser.cpp object/Object.cpp interpreter/Interpreter.cpp symboltable/SymbolTable.cpp /link /out:build/BarkScript.exe diff --git a/context/context.h b/context/context.h index 19d8da8..b76bcd0 100644 --- a/context/context.h +++ b/context/context.h @@ -4,6 +4,7 @@ #include #include #include "../position/position.h" +#include "../symboltable/symboltable.h" struct Context; @@ -13,6 +14,7 @@ struct Context { std::string displayName = "UNKNOWN_CONTEXT_NAME"; spContext parent; Position parentEntryPosition; + spSymbolTable symbolTable; Context(std::string displayName, spContext parent = nullptr, Position parentEntryPosition = Position()) { this->displayName = displayName; diff --git a/interpreter/Interpreter.cpp b/interpreter/Interpreter.cpp index 8df1d59..bd2a7d9 100644 --- a/interpreter/Interpreter.cpp +++ b/interpreter/Interpreter.cpp @@ -33,6 +33,10 @@ RuntimeResult Interpreter::visit(spNode node, spContext context) { if (type == nodetypes::Number) { return visitNumberNode(node, context); + } else if (type == nodetypes::VariableAssignment) { + return visitVariableAssignmentNode(node, context); + } else if (type == nodetypes::VariableRetrievement) { + return visitVariableRetrievementNode(node, context); } else if (type == nodetypes::BinaryOperator) { return visitBinaryOperatorNode(node, context); } else if (type == nodetypes::UnaryOperator) { @@ -49,6 +53,28 @@ RuntimeResult Interpreter::visitNumberNode(spNode node, spContext context) { return RuntimeResult().success(number); } +RuntimeResult Interpreter::visitVariableAssignmentNode(spNode node, spContext context) { + RuntimeResult rt; + std::string variableName = node->token.value; + spObject value = rt.registerRT(visit(node->valueNode, context)); + if (rt.hasError()) return rt; + bool success = context->symbolTable->set(variableName, value, true); + if (!success) + return rt.failure(makeSharedError(RuntimeError(node->token.positionStart, node->positionEnd, "Variable not reassigned properly", context))); + return rt.success(value); +} + +RuntimeResult Interpreter::visitVariableRetrievementNode(spNode node, spContext context) { + RuntimeResult rt; + std::string variableName = node->token.value; + spObject value = context->symbolTable->get(variableName); + if (value == nullptr) + return rt.failure(makeSharedError(RuntimeError(node->positionStart, node->positionEnd, "Variable \"" + variableName + "\" is not defined in the current scope!", context))); + value = value->copy(); + value->setPosition(node->positionStart, node->positionEnd); + return rt.success(value); +} + RuntimeResult Interpreter::visitBinaryOperatorNode(spNode node, spContext context) { RuntimeResult rt; spObject left = rt.registerRT(visit(node->leftNode, context)); diff --git a/interpreter/interpreter.h b/interpreter/interpreter.h index 22cd375..9fa493f 100644 --- a/interpreter/interpreter.h +++ b/interpreter/interpreter.h @@ -25,6 +25,8 @@ struct Interpreter { RuntimeResult visit(spNode node, spContext context); RuntimeResult visitNumberNode(spNode node, spContext context); + RuntimeResult visitVariableAssignmentNode(spNode node, spContext context); + RuntimeResult visitVariableRetrievementNode(spNode node, spContext context); RuntimeResult visitBinaryOperatorNode(spNode node, spContext context); RuntimeResult visitUnaryOperatorNode(spNode node, spContext context); }; diff --git a/lexer/Lexer.cpp b/lexer/Lexer.cpp index 8b0dae0..0faaacb 100644 --- a/lexer/Lexer.cpp +++ b/lexer/Lexer.cpp @@ -4,6 +4,7 @@ #include "../token/tokens.h" #include "../position/position.h" #include "../error/error.h" +#include "../reservedwords/reservedwords.h" Lexer::Lexer(std::string input, std::string filename) { this->input = input; @@ -82,6 +83,11 @@ SingleLexResult Lexer::nextToken() { token = Token(tokens::CLOSE_PAREN, ")", position, position); break; } + case '=': + { + token = Token(tokens::EQUAL, "=", position, position); + break; + } case ' ': { readChar(); @@ -107,6 +113,15 @@ SingleLexResult Lexer::nextToken() { } token = Token(tokens::NUMBER, value, positionStart, position); break; + } else if (isIdentifierStarter(current)) { + std::string value = std::string(1, current); + Position positionStart = position.copy(); + while (isIdentifierCharacter(peekChar())) { + value += peekChar(); + readChar(); + } + token = Token(isReservedWord(value) ? tokens::KEYWORD : tokens::IDENTIFIER, value, positionStart, position); + break; } else { Position positionStart = position.copy(); char tempCurrent = current; @@ -120,7 +135,23 @@ SingleLexResult Lexer::nextToken() { } bool Lexer::isNumeric(char c) { - return 48 <= c && c <= 57; + return '0' <= c && c <= '9'; +} + +bool Lexer::isAlpha(char c) { + return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); +} + +bool Lexer::isUnderscore(char c) { + return c == '_'; +} + +bool Lexer::isIdentifierStarter(char c) { + return isAlpha(c) || isUnderscore(c); +} + +bool Lexer::isIdentifierCharacter(char c) { + return isIdentifierStarter(c) || isNumeric(c); } MultiLexResult Lexer::tokenize() { diff --git a/lexer/lexer.h b/lexer/lexer.h index 1447861..1a9b1f4 100644 --- a/lexer/lexer.h +++ b/lexer/lexer.h @@ -47,6 +47,10 @@ struct Lexer { void readChar(); char peekChar(int num); bool isNumeric(char c); + bool isAlpha(char c); + bool isUnderscore(char c); + bool isIdentifierStarter(char c); + bool isIdentifierCharacter(char c); SingleLexResult nextToken(); MultiLexResult tokenize(); diff --git a/object/Object.cpp b/object/Object.cpp index 426334f..1096e31 100644 --- a/object/Object.cpp +++ b/object/Object.cpp @@ -66,6 +66,19 @@ std::string Number::to_string() { return out.str(); } +spObject Number::copy() { + spObject other = makeSharedObject(Number()); + other->doubleValue = this->doubleValue; + other->sign = this->sign; + other->isPureDouble = this->isPureDouble; + other->isPureZero = this->isPureZero; + other->isInfinity = this->isInfinity; + other->isNaN = this->isNaN; + other->setPosition(this->positionStart, this->positionEnd); + other->setContext(this->context); + return other; +} + RuntimeResult Number::binary_plus(spObject other) { RuntimeResult rt; diff --git a/object/object.h b/object/object.h index a91007c..77a7b1a 100644 --- a/object/object.h +++ b/object/object.h @@ -42,10 +42,11 @@ struct Object { Position positionEnd; spContext context; - void setPosition(Position positionStart, Position postionEnd); + void setPosition(Position positionStart, Position positionEnd); void setContext(spContext context); std::string virtual to_string() { return "to_string is not implemented for type " + this->type; }; + spObject virtual copy() = 0; RuntimeResult virtual binary_plus(spObject other) { return RuntimeResult().failure(makeSharedError(RuntimeError(this->positionStart, other->positionEnd, "binary_plus for " + this->type + " is not implemented!", this->context))); }; RuntimeResult virtual binary_minus(spObject other) { return RuntimeResult().failure(makeSharedError(RuntimeError(this->positionStart, other->positionEnd, "binary_minus for " + this->type + " is not implemented!", this->context))); }; @@ -59,10 +60,12 @@ struct Object { }; struct Number : Object { + Number() { this->type = "Number"; }; Number(double value, bool sign = +1); Number(std::string value, bool sign = +1); std::string to_string() override; + spObject copy() override; RuntimeResult binary_plus(spObject other) override; RuntimeResult binary_minus(spObject other) override; diff --git a/parser/Parser.cpp b/parser/Parser.cpp index 99c4eb9..2b14268 100644 --- a/parser/Parser.cpp +++ b/parser/Parser.cpp @@ -5,6 +5,7 @@ #include "../token/token.h" #include "../token/tokens.h" #include "../ast/ast.h" +#include "../reservedwords/reservedwords.h" // https://stackoverflow.com/a/20303915/12101554 bool in_array(const std::string& value, const std::vector& array) { @@ -31,6 +32,9 @@ ParseResult Parser::atom() { if (token.type == tokens::NUMBER) { nextToken(); return pr.success(makeSharedNode(NumberNode(token))); + } else if (token.type == tokens::IDENTIFIER) { + nextToken(); + return pr.success(makeSharedNode(VariableRetrievementNode(token))); } else if (token.type == tokens::OPEN_PAREN) { nextToken(); spNode exprRes = pr.registerPR(expr()); @@ -69,8 +73,23 @@ ParseResult Parser::term() { } ParseResult Parser::expr() { - std::function rule = [this]() { return term(); }; - return binaryOperation(rule, { tokens::PLUS, tokens::MINUS }); + if (currentToken.matches(tokens::KEYWORD, reservedWords::LET)) { + ParseResult pr; + nextToken(); + if (!currentToken.matches(tokens::IDENTIFIER)) + return pr.failure(makeSharedError(InvalidSyntaxError(currentToken.positionStart, currentToken.positionEnd, "Expected an identifier"))); + Token variableNameToken = currentToken; + nextToken(); + if (!currentToken.matches(tokens::EQUAL)) + return pr.failure(makeSharedError(InvalidSyntaxError(currentToken.positionStart, currentToken.positionEnd, "Expected an '='"))); + nextToken(); + spNode exprRes = pr.registerPR(expr()); + if (pr.hasError()) return pr; + return pr.success(makeSharedNode(VariableAssignmentNode(variableNameToken, exprRes))); + } else { + std::function rule = [this]() { return term(); }; + return binaryOperation(rule, { tokens::PLUS, tokens::MINUS }); + } } ParseResult Parser::parse() { diff --git a/reservedwords/reservedwords.h b/reservedwords/reservedwords.h new file mode 100644 index 0000000..d49324d --- /dev/null +++ b/reservedwords/reservedwords.h @@ -0,0 +1,21 @@ +#pragma once +#ifndef RESERVEDWORDS_H +#define RESERVEDWORDS_H +#include +#include + +namespace reservedWords { + using namespace std; + + const string LET = "let"; +} + +const std::string reservedWordsArray[] = { + reservedWords::LET, +}; + +inline bool isReservedWord(std::string identifier) { + return std::find(std::begin(reservedWordsArray), std::end(reservedWordsArray), identifier) != std::end(reservedWordsArray); +} + +#endif // !RESERVEDWORDS_H diff --git a/symboltable/SymbolTable.cpp b/symboltable/SymbolTable.cpp new file mode 100644 index 0000000..19dbbba --- /dev/null +++ b/symboltable/SymbolTable.cpp @@ -0,0 +1,37 @@ +#include "symboltable.h" +#include +#include +#include "../object/object.h" + + +spObject SymbolTable::get(std::string key) { + if (symbols.find(key) == symbols.end()) { + // Key not found + if (parent != nullptr) { + return parent->get(key); + } else { + // throw error + return nullptr; + } + } else { + return symbols.at(key); + } +} + +bool SymbolTable::set(std::string key, spObject value, bool currentContext) { + // Returning `false` signifies that there was an error + if (currentContext) { + symbols[key] = value; + return true; + } else { + if (symbols.find(key) != symbols.end()) { + symbols[key] = value; + return true; + } else if (parent != nullptr) { + return parent->set(key, value, false); + } else { + // The symbol doesn't exist anywhere and we weren't told to make it + return false; + } + } +} diff --git a/symboltable/symboltable.h b/symboltable/symboltable.h new file mode 100644 index 0000000..46e19f5 --- /dev/null +++ b/symboltable/symboltable.h @@ -0,0 +1,25 @@ +#pragma once +#ifndef SYMBOLTABLE_H +#define SYMBOLTABLE_H +#include +#include +#include + +struct Object; + +typedef std::shared_ptr spObject; + +struct SymbolTable; + +typedef std::shared_ptr spSymbolTable; + +struct SymbolTable { + std::unordered_map symbols; + spSymbolTable parent = nullptr; + + spObject get(std::string key); + bool set(std::string key, spObject value, bool currentContext = false); +}; + + +#endif // !SYMBOLTABLE_H diff --git a/syntax.txt b/syntax.txt index e1d3a5b..70c5ba0 100644 --- a/syntax.txt +++ b/syntax.txt @@ -1,4 +1,5 @@ -expr : term ((PLUS|MINUS) term)* +expr : KEYWORD:LET IDENTIFIER EQUAL expr + : term ((PLUS|MINUS) term)* term : factor ((ASTERISK|F_SLASH|DOUBLE_F_SLASH) factor)* @@ -8,4 +9,5 @@ factor : (PLUS|MINUS) factor exponent : atom (DOUBLE_ASTERISK factor)* atom : NUMBER + : IDENTIFIER : OPEN_PAREN expr CLOSE_PAREN diff --git a/token/token.h b/token/token.h index 8680ec9..1dd40ef 100644 --- a/token/token.h +++ b/token/token.h @@ -27,6 +27,14 @@ struct Token { std::string to_string() { return "Token(" + type + ", \"" + value + "\", " + std::to_string(positionStart.index) + ", " + std::to_string(positionEnd.index) + ")"; } + + bool matches(std::string tokenType) { + return this->type == tokenType; + } + + bool matches(std::string tokenType, std::string value) { + return this->type == tokenType && this->value == value; + } }; #endif // !TOKEN_H diff --git a/token/tokens.h b/token/tokens.h index 0de81c7..5b020c6 100644 --- a/token/tokens.h +++ b/token/tokens.h @@ -17,6 +17,11 @@ namespace tokens { const string OPEN_PAREN = "OPEN_PAREN"; const string CLOSE_PAREN = "CLOSE_PAREN"; + // Keywords and Random Identifiers + const string KEYWORD = "KEYWORD"; + const string IDENTIFIER = "IDENTIFIER"; + const string EQUAL = "EQUAL"; + // Data types const string NUMBER = "NUMBER";