From 7427f43646f8a9851c3b32e949dcf0c4e68e6438 Mon Sep 17 00:00:00 2001 From: Kevin Traini Date: Tue, 14 Jan 2025 19:21:27 +0100 Subject: [PATCH 1/5] feat: Introduce array initialization Part of #56 Also introduce a TypeBuilder missing from pointers pr -> it builds non native types, for example int*. Needed for array element type cast. --- CMakeLists.txt | 6 +- include/filc/grammar/DumpVisitor.h | 2 + include/filc/grammar/Type.h | 19 ++++ include/filc/grammar/Visitor.h | 2 + include/filc/grammar/array/Array.h | 51 ++++++++++ include/filc/grammar/ast.h | 2 + include/filc/llvm/IRGenerator.h | 2 + include/filc/validation/TypeBuilder.h | 50 ++++++++++ include/filc/validation/ValidationVisitor.h | 13 ++- src/grammar/DumpVisitor.cpp | 12 +++ src/grammar/FilLexer.g4 | 3 + src/grammar/FilParser.g4 | 28 +++++- src/grammar/Type.cpp | 26 +++++ src/grammar/array/Array.cpp | 44 +++++++++ src/llvm/IRGenerator.cpp | 5 + src/validation/Environment.cpp | 4 + src/validation/TypeBuilder.cpp | 76 +++++++++++++++ src/validation/ValidationVisitor.cpp | 66 ++++++++++++- tests/unit/grammar/TypeTest.cpp | 15 +++ tests/unit/grammar/array/ArrayTest.cpp | 56 +++++++++++ tests/unit/test_tools.cpp | 10 ++ tests/unit/test_tools.h | 2 + tests/unit/validation/TypeBuilderTest.cpp | 95 +++++++++++++++++++ .../unit/validation/ValidationVisitorTest.cpp | 57 +++++++++++ 24 files changed, 636 insertions(+), 10 deletions(-) create mode 100644 include/filc/grammar/array/Array.h create mode 100644 include/filc/validation/TypeBuilder.h create mode 100644 src/grammar/array/Array.cpp create mode 100644 src/validation/TypeBuilder.cpp create mode 100644 tests/unit/grammar/array/ArrayTest.cpp create mode 100644 tests/unit/validation/TypeBuilderTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 544f287..75b99ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ file(READ ${CMAKE_CURRENT_SOURCE_DIR}/VERSION FILC_VERSION) project( filc VERSION ${FILC_VERSION} - LANGUAGES CXX + LANGUAGES C CXX ) include(FetchContent) @@ -91,8 +91,8 @@ message(DEBUG SRC_FILES=${SRC_FILES}) add_library(filc_lib ${SRC_FILES} ${ANTLR_Lexer_CXX_OUTPUTS} ${ANTLR_Parser_CXX_OUTPUTS}) target_link_libraries(filc_lib PRIVATE additional_config cxxopts::cxxopts antlr4_static ${llvm_libs} ${llvm_targets}) -target_include_directories(filc_lib PUBLIC - "${PROJECT_SOURCE_DIR}/include" +target_include_directories(filc_lib PUBLIC "${PROJECT_SOURCE_DIR}/include") +target_include_directories(filc_lib SYSTEM PUBLIC ${cxxopts_INCLUDE_DIR} ${ANTLR4_INCLUDE_DIR} ${ANTLR_Lexer_OUTPUT_DIR} diff --git a/include/filc/grammar/DumpVisitor.h b/include/filc/grammar/DumpVisitor.h index dfe3597..638c05b 100644 --- a/include/filc/grammar/DumpVisitor.h +++ b/include/filc/grammar/DumpVisitor.h @@ -58,6 +58,8 @@ class DumpVisitor final: public Visitor { auto visitVariableAddress(VariableAddress *address) -> void override; + auto visitArray(Array *array) -> void override; + private: std::ostream &_out; int _indent_level; diff --git a/include/filc/grammar/Type.h b/include/filc/grammar/Type.h index 88a0d57..ca75a55 100644 --- a/include/filc/grammar/Type.h +++ b/include/filc/grammar/Type.h @@ -86,6 +86,25 @@ class PointerType final : public AbstractType { std::shared_ptr _pointed_type; }; +class ArrayType final : public AbstractType { + public: + ArrayType(unsigned int size, std::shared_ptr contained_type); + + [[nodiscard]] auto getName() const noexcept -> std::string override; + + [[nodiscard]] auto getDisplayName() const noexcept -> std::string override; + + [[nodiscard]] auto toDisplay() const noexcept -> std::string override; + + [[nodiscard]] auto getContainedType() const noexcept -> std::shared_ptr; + + auto generateLLVMType(llvm::LLVMContext *context) -> void override; + + private: + unsigned int _size; + std::shared_ptr _contained_type; +}; + class AliasType final : public AbstractType { public: AliasType(std::string name, std::shared_ptr aliased_type); diff --git a/include/filc/grammar/Visitor.h b/include/filc/grammar/Visitor.h index d64d86c..17dfe9b 100644 --- a/include/filc/grammar/Visitor.h +++ b/include/filc/grammar/Visitor.h @@ -59,6 +59,8 @@ template class Visitor { virtual auto visitVariableAddress(VariableAddress *address) -> Return = 0; + virtual auto visitArray(Array *array) -> Return = 0; + protected: Visitor() = default; }; diff --git a/include/filc/grammar/array/Array.h b/include/filc/grammar/array/Array.h new file mode 100644 index 0000000..9670ed9 --- /dev/null +++ b/include/filc/grammar/array/Array.h @@ -0,0 +1,51 @@ +/** + * MIT License + * + * Copyright (c) 2025-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef FILC_ARRAY_H +#define FILC_ARRAY_H + +#include "filc/grammar/expression/Expression.h" + +#include +#include + +namespace filc { +class Array final : public Expression { + public: + explicit Array(const std::vector> &values); + + [[nodiscard]] auto getValues() const -> const std::vector> &; + + [[nodiscard]] auto getSize() const -> unsigned int; + + auto acceptVoidVisitor(Visitor *visitor) -> void override; + + auto acceptIRVisitor(Visitor *visitor) -> llvm::Value * override; + + private: + unsigned long _size; + std::vector> _values; +}; +} // namespace filc + +#endif // FILC_ARRAY_H diff --git a/include/filc/grammar/ast.h b/include/filc/grammar/ast.h index 5b24f80..2374c5a 100644 --- a/include/filc/grammar/ast.h +++ b/include/filc/grammar/ast.h @@ -55,6 +55,8 @@ class Pointer; class PointerDereferencing; class VariableAddress; + +class Array; } #endif // FILC_AST_H diff --git a/include/filc/llvm/IRGenerator.h b/include/filc/llvm/IRGenerator.h index 93e950c..524df68 100644 --- a/include/filc/llvm/IRGenerator.h +++ b/include/filc/llvm/IRGenerator.h @@ -68,6 +68,8 @@ class IRGenerator final: public Visitor { auto visitVariableAddress(VariableAddress *address) -> llvm::Value * override; + auto visitArray(Array *array) -> llvm::Value * override; + private: std::unique_ptr _llvm_context; std::unique_ptr _module; diff --git a/include/filc/validation/TypeBuilder.h b/include/filc/validation/TypeBuilder.h new file mode 100644 index 0000000..acd87f8 --- /dev/null +++ b/include/filc/validation/TypeBuilder.h @@ -0,0 +1,50 @@ +/** + * MIT License + * + * Copyright (c) 2025-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef FILC_TYPEBUILDER_H +#define FILC_TYPEBUILDER_H + +#include "filc/validation/Environment.h" + +#include +#include + +namespace filc { +class TypeBuilder final { + public: + explicit TypeBuilder(Environment *environment); + + [[nodiscard]] auto tryBuildType(const std::string &type) const noexcept -> bool; + + private: + Environment *_environment; + std::regex _pointer_type_regex; + std::regex _array_type_regex; + + [[nodiscard]] auto tryBuildPointerType(const std::string &type) const noexcept -> bool; + + [[nodiscard]] auto tryBuildArrayType(const std::string &type) const noexcept -> bool; +}; +} // namespace filc + +#endif // FILC_TYPEBUILDER_H diff --git a/include/filc/validation/ValidationVisitor.h b/include/filc/validation/ValidationVisitor.h index 82c11ca..f2775a0 100644 --- a/include/filc/validation/ValidationVisitor.h +++ b/include/filc/validation/ValidationVisitor.h @@ -24,15 +24,17 @@ #ifndef FILC_VALIDATIONVISITOR_H #define FILC_VALIDATIONVISITOR_H -#include "filc/grammar/Visitor.h" #include "filc/grammar/Position.h" +#include "filc/grammar/Visitor.h" #include "filc/validation/Environment.h" +#include "filc/validation/TypeBuilder.h" + +#include +#include #include #include -#include -#include -#include #include +#include namespace filc { class ValidationContext final { @@ -96,9 +98,12 @@ class ValidationVisitor final : public Visitor { auto visitVariableAddress(VariableAddress *address) -> void override; + auto visitArray(Array *array) -> void override; + private: std::unique_ptr _context; std::unique_ptr _environment; + TypeBuilder _type_builder; std::ostream &_out; bool _error; diff --git a/src/grammar/DumpVisitor.cpp b/src/grammar/DumpVisitor.cpp index 600ce24..df66676 100644 --- a/src/grammar/DumpVisitor.cpp +++ b/src/grammar/DumpVisitor.cpp @@ -31,6 +31,8 @@ #include "filc/grammar/program/Program.h" #include "filc/grammar/variable/Variable.h" +#include + using namespace filc; DumpVisitor::DumpVisitor(std::ostream &out): _out(out), _indent_level(0) {} @@ -163,6 +165,16 @@ auto DumpVisitor::visitVariableAddress(VariableAddress *address) -> void { _out << "[VariableAddress:" << address->getName() << "]\n"; } +auto DumpVisitor::visitArray(Array *array) -> void { + printIdent(); + _out << "[Array:" << array->getSize() << "]\n"; + _indent_level++; + for (const auto &value : array->getValues()) { + value->acceptVoidVisitor(this); + } + _indent_level--; +} + auto DumpVisitor::printIdent() const -> void { _out << std::string(_indent_level, '\t'); } diff --git a/src/grammar/FilLexer.g4 b/src/grammar/FilLexer.g4 index fe462fb..2c33e5c 100644 --- a/src/grammar/FilLexer.g4 +++ b/src/grammar/FilLexer.g4 @@ -49,6 +49,9 @@ GT: '>'; GTE: '>='; LPAREN: '('; RPAREN: ')'; +LBRACK: '['; +RBRACK: ']'; +COMMA: ','; AMP: '&'; // Assignation operators diff --git a/src/grammar/FilParser.g4 b/src/grammar/FilParser.g4 index b769328..86e4d4c 100644 --- a/src/grammar/FilParser.g4 +++ b/src/grammar/FilParser.g4 @@ -36,6 +36,7 @@ options { #include "filc/grammar/identifier/Identifier.h" #include "filc/grammar/assignation/Assignation.h" #include "filc/grammar/pointer/Pointer.h" +#include "filc/grammar/array/Array.h" #include #include } @@ -70,6 +71,9 @@ expression returns[std::shared_ptr tree] | po=pointer_operation { $tree = $po.tree; } + | ar=array { + $tree = $ar.tree; + } // === Binary calcul === | el3=expression op3=MOD er3=expression { @@ -156,7 +160,7 @@ variable_declaration returns[std::shared_ptr tree] value = $value.tree; })?; -type : IDENTIFIER STAR?; +type : IDENTIFIER (STAR | LBRACK INTEGER RBRACK)?; assignation returns[std::shared_ptr tree] : i1=IDENTIFIER EQ e1=expression { @@ -180,3 +184,25 @@ pointer_operation returns[std::shared_ptr tree] | AMP i=IDENTIFIER { $tree = std::make_shared($i.text); }; + +array returns[std::shared_ptr tree] +@init { + std::vector> values; +} +@after { + $tree = std::make_shared(values); +} + : LBRACK (v=array_values { + values = $v.values; + })? RBRACK; + +array_values returns[std::vector> values] +@init { + $values = std::vector>(); +} + : e=expression { + $values.push_back($e.tree); + } (COMMA v=array_values { + auto values_to_insert = $v.values; + $values.insert($values.end(), values_to_insert.begin(), values_to_insert.end()); + })?; diff --git a/src/grammar/Type.cpp b/src/grammar/Type.cpp index 6575310..e3f79b2 100644 --- a/src/grammar/Type.cpp +++ b/src/grammar/Type.cpp @@ -83,6 +83,32 @@ auto PointerType::generateLLVMType(llvm::LLVMContext *context) -> void { setLLVMType(llvm::PointerType::getUnqual(_pointed_type->getLLVMType(context))); } +ArrayType::ArrayType(const unsigned int size, std::shared_ptr contained_type) + : _size(size), _contained_type(std::move(contained_type)) {} + +auto ArrayType::getName() const noexcept -> std::string { + return _contained_type->getName() + "[" + std::to_string(_size) + "]"; +} + +auto ArrayType::getDisplayName() const noexcept -> std::string { + return _contained_type->getDisplayName() + "[" + std::to_string(_size) + "]"; +} + +auto ArrayType::getContainedType() const noexcept -> std::shared_ptr { + return _contained_type; +} + +auto ArrayType::toDisplay() const noexcept -> std::string { + if (_contained_type->getName() != _contained_type->getDisplayName()) { + return getDisplayName() + " aka " + getName(); + } + return getName(); +} + +auto ArrayType::generateLLVMType(llvm::LLVMContext *context) -> void { + setLLVMType(llvm::ArrayType::get(_contained_type->getLLVMType(context), _size)); +} + AliasType::AliasType(std::string name, std::shared_ptr aliased_type) : _name(std::move(name)), _aliased_type(std::move(aliased_type)) {} diff --git a/src/grammar/array/Array.cpp b/src/grammar/array/Array.cpp new file mode 100644 index 0000000..f54e935 --- /dev/null +++ b/src/grammar/array/Array.cpp @@ -0,0 +1,44 @@ +/** + * MIT License + * + * Copyright (c) 2025-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "filc/grammar/array/Array.h" + +using namespace filc; + +Array::Array(const std::vector> &values): _size(values.size()), _values(values) {} + +auto Array::getValues() const -> const std::vector> & { + return _values; +} + +auto Array::getSize() const -> unsigned int { + return _size; +} + +auto Array::acceptVoidVisitor(Visitor *visitor) -> void { + visitor->visitArray(this); +} + +auto Array::acceptIRVisitor(Visitor *visitor) -> llvm::Value * { + return visitor->visitArray(this); +} diff --git a/src/llvm/IRGenerator.cpp b/src/llvm/IRGenerator.cpp index 7025205..66dbbef 100644 --- a/src/llvm/IRGenerator.cpp +++ b/src/llvm/IRGenerator.cpp @@ -204,3 +204,8 @@ auto IRGenerator::visitVariableAddress(VariableAddress *address) -> llvm::Value return alloca; } + +auto IRGenerator::visitArray(Array *array) -> llvm::Value * { + // TODO: alloc array size + add values, return pointer to first value + return nullptr; +} diff --git a/src/validation/Environment.cpp b/src/validation/Environment.cpp index e479f68..47fc2f3 100644 --- a/src/validation/Environment.cpp +++ b/src/validation/Environment.cpp @@ -50,6 +50,8 @@ Environment::Environment() { addType(std::make_shared("char", getType("u8"))); addType(std::make_shared(getType("char"))); + + addType(std::make_shared("void")); } auto Environment::prepareLLVMTypes(llvm::LLVMContext *context) const -> void { @@ -74,6 +76,8 @@ auto Environment::prepareLLVMTypes(llvm::LLVMContext *context) const -> void { getType("char")->setLLVMType(llvm::Type::getInt8Ty(*context)); getType("char*")->setLLVMType(llvm::PointerType::get(llvm::Type::getInt8Ty(*context), 0)); + + getType("void")->setLLVMType(llvm::Type::getVoidTy(*context)); } auto Environment::hasType(const std::string &name) const -> bool { diff --git a/src/validation/TypeBuilder.cpp b/src/validation/TypeBuilder.cpp new file mode 100644 index 0000000..d618a5b --- /dev/null +++ b/src/validation/TypeBuilder.cpp @@ -0,0 +1,76 @@ +/** + * MIT License + * + * Copyright (c) 2025-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "filc/validation/TypeBuilder.h" + +#include "filc/grammar/Type.h" + +#include + +using namespace filc; + +TypeBuilder::TypeBuilder(Environment *environment) + : _environment(environment), _pointer_type_regex("^(.*)\\*$"), _array_type_regex("^(.*)\\[(\\d+)]$") {} + +auto TypeBuilder::tryBuildType(const std::string &type) const noexcept -> bool { + if (tryBuildPointerType(type)) { + return true; + } + + if (tryBuildArrayType(type)) { + return true; + } + + return false; +} + +auto TypeBuilder::tryBuildPointerType(const std::string &type) const noexcept -> bool { + std::smatch result; + if (std::regex_match(type, result, _pointer_type_regex)) { + const auto sub_type = result[1].str(); + if (! _environment->hasType(sub_type) && ! tryBuildType(sub_type)) { + return false; + } + + _environment->addType(std::make_shared(_environment->getType(sub_type))); + return true; + } + + return false; +} + +auto TypeBuilder::tryBuildArrayType(const std::string &type) const noexcept -> bool { + std::smatch result; + if (std::regex_match(type, result, _array_type_regex)) { + const auto sub_type = result[1].str(); + if (! _environment->hasType(sub_type) && ! tryBuildType(sub_type)) { + return false; + } + + const auto size = std::stoi(result[2].str()); + _environment->addType(std::make_shared(size, _environment->getType(sub_type))); + return true; + } + + return false; +} diff --git a/src/validation/ValidationVisitor.cpp b/src/validation/ValidationVisitor.cpp index c1063b1..1babcdc 100644 --- a/src/validation/ValidationVisitor.cpp +++ b/src/validation/ValidationVisitor.cpp @@ -23,6 +23,7 @@ */ #include "filc/validation/ValidationVisitor.h" +#include "filc/grammar/array/Array.h" #include "filc/grammar/assignation/Assignation.h" #include "filc/grammar/calcul/Calcul.h" #include "filc/grammar/identifier/Identifier.h" @@ -38,7 +39,8 @@ using namespace filc; ValidationVisitor::ValidationVisitor(std::ostream &out) - : _context(new ValidationContext()), _environment(new Environment()), _out(out), _error(false) {} + : _context(new ValidationContext()), _environment(new Environment()), _type_builder(_environment.get()), _out(out), + _error(false) {} auto ValidationVisitor::getEnvironment() const -> const Environment * { return _environment.get(); @@ -160,7 +162,7 @@ auto ValidationVisitor::visitVariableDeclaration(VariableDeclaration *variable) std::shared_ptr variable_type = nullptr; if (! variable->getTypeName().empty()) { - if (! _environment->hasType(variable->getTypeName())) { + if (! _environment->hasType(variable->getTypeName()) && ! _type_builder.tryBuildType(variable->getTypeName())) { displayError("Unknown type: " + variable->getTypeName(), variable->getPosition()); return; } @@ -373,3 +375,63 @@ auto ValidationVisitor::visitVariableAddress(VariableAddress *address) -> void { displayWarning("Value not used", address->getPosition()); } } + +auto ValidationVisitor::visitArray(Array *array) -> void { + if (array->getSize() == 0) { + if (_context->has("cast_type")) { + array->setType(_context->get>("cast_type")); + } else { + if (! _environment->hasType("void[0]")) { + _environment->addType(std::make_shared(0, _environment->getType("void"))); + } + array->setType(_environment->getType("void[0]")); + } + } else { + if (_context->has("cast_type")) { + const auto cast_type = _context->get>("cast_type"); + const auto array_type = std::dynamic_pointer_cast(cast_type); + if (array_type == nullptr) { + displayError( + "Cannot cast an array to a type not corresponding to an array: " + cast_type->toDisplay(), + array->getPosition() + ); + return; + } + + _context->stack(); + _context->set("cast_type", array_type->getContainedType()); + _context->set("return", true); + } + + std::vector> values_types; + for (const auto &value : array->getValues()) { + value->acceptVoidVisitor(this); + + const auto value_type = value->getType(); + if (value_type == nullptr) { + return; + } + values_types.push_back(value_type); + } + + if (_context->has("cast_type")) { + _context->unstack(); + } + + const auto it = std::adjacent_find(values_types.begin(), values_types.end(), std::not_equal_to<>()); + if (it != values_types.end()) { + displayError("All values of an array should be of the same type", array->getPosition()); + return; + } + + const auto type_name = values_types[0]->getDisplayName() + "[" + std::to_string(array->getSize()) + "]"; + if (! _environment->hasType(type_name)) { + _environment->addType(std::make_shared(array->getSize(), values_types[0])); + } + array->setType(_environment->getType(type_name)); + } + + if (! _context->has("return") || ! _context->get("return")) { + displayWarning("Value not used", array->getPosition()); + } +} diff --git a/tests/unit/grammar/TypeTest.cpp b/tests/unit/grammar/TypeTest.cpp index 62e991d..22569f8 100644 --- a/tests/unit/grammar/TypeTest.cpp +++ b/tests/unit/grammar/TypeTest.cpp @@ -49,6 +49,21 @@ TEST(PonterType, getPointedType) { ASSERT_STREQ("int", type.getPointedType()->getDisplayName().c_str()); } +TEST(ArrayType, getName) { + const filc::ArrayType type(2, std::make_shared("int")); + ASSERT_STREQ("int[2]", type.getName().c_str()); +} + +TEST(ArrayType, getDisplayName) { + const filc::ArrayType type(2, std::make_shared("int")); + ASSERT_STREQ("int[2]", type.getDisplayName().c_str()); +} + +TEST(ArrayType, getContainedType) { + const filc::ArrayType type(2, std::make_shared("int")); + ASSERT_STREQ("int", type.getContainedType()->getName().c_str()); +} + TEST(AliasType, getName) { const filc::AliasType type("char", std::make_shared("u8")); ASSERT_STREQ("u8", type.getName().c_str()); diff --git a/tests/unit/grammar/array/ArrayTest.cpp b/tests/unit/grammar/array/ArrayTest.cpp new file mode 100644 index 0000000..394cd1b --- /dev/null +++ b/tests/unit/grammar/array/ArrayTest.cpp @@ -0,0 +1,56 @@ +/** + * MIT License + * + * Copyright (c) 2025-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "test_tools.h" + +#include +#include +#include +#include + +using namespace ::testing; + +TEST(Array, parsingEmpty) { + const auto program = parseString("[]"); + const auto expressions = program->getExpressions(); + ASSERT_THAT(expressions, SizeIs(1)); + const auto array = std::dynamic_pointer_cast(expressions[0]); + ASSERT_NE(nullptr, array); + ASSERT_EQ(0, array->getSize()); + ASSERT_THAT(array->getValues(), IsEmpty()); +} + +TEST(Array, parsingSimple) { + const auto program = parseString("[1, 2, 3]"); + const auto expressions = program->getExpressions(); + ASSERT_THAT(expressions, SizeIs(1)); + const auto array = std::dynamic_pointer_cast(expressions[0]); + ASSERT_NE(nullptr, array); + ASSERT_EQ(3, array->getSize()); + const auto values = array->getValues(); + for (unsigned int i = 0; i < array->getSize(); i++) { + const auto value = std::dynamic_pointer_cast(values[i]); + ASSERT_NE(nullptr, value); + ASSERT_EQ(i + 1, value->getValue()); + } +} diff --git a/tests/unit/test_tools.cpp b/tests/unit/test_tools.cpp index 7c1732f..ba8e7f7 100644 --- a/tests/unit/test_tools.cpp +++ b/tests/unit/test_tools.cpp @@ -27,6 +27,7 @@ #include "FilParser.h" #include "antlr4-runtime.h" +#include #include auto toStringArray(const std::vector &data) -> std::vector { @@ -134,6 +135,15 @@ auto PrinterVisitor::visitVariableAddress(filc::VariableAddress *address) -> voi _out << "&" << address->getName(); } +auto PrinterVisitor::visitArray(filc::Array *array) -> void { + _out << "["; + for (const auto &value : array->getValues()) { + value->acceptVoidVisitor(this); + _out << ", "; + } + _out << "]"; +} + TokenSourceStub::TokenSourceStub(std::string filename): _filename(std::move(filename)) {} auto TokenSourceStub::nextToken() -> std::unique_ptr { diff --git a/tests/unit/test_tools.h b/tests/unit/test_tools.h index 6fd7901..de7d636 100644 --- a/tests/unit/test_tools.h +++ b/tests/unit/test_tools.h @@ -70,6 +70,8 @@ class PrinterVisitor final: public filc::Visitor { auto visitVariableAddress(filc::VariableAddress *address) -> void override; + auto visitArray(filc::Array *array) -> void override; + private: std::stringstream _out; }; diff --git a/tests/unit/validation/TypeBuilderTest.cpp b/tests/unit/validation/TypeBuilderTest.cpp new file mode 100644 index 0000000..b1534db --- /dev/null +++ b/tests/unit/validation/TypeBuilderTest.cpp @@ -0,0 +1,95 @@ +/** + * MIT License + * + * Copyright (c) 2025-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "test_tools.h" + +#include +#include +#include +#include + +TEST(TypeBuilder, false_empty) { + const filc::TypeBuilder type_builder(new filc::Environment()); + + ASSERT_FALSE(type_builder.tryBuildType("")); +} + +TEST(TypeBuilder, false_notAType) { + const filc::TypeBuilder type_builder(new filc::Environment()); + + ASSERT_FALSE(type_builder.tryBuildType("foo_bar")); +} + +TEST(TypeBuilder, true_simplePointer) { + filc::Environment environment; + const filc::TypeBuilder type_builder(&environment); + + ASSERT_FALSE(environment.hasType("int*")); + ASSERT_TRUE(type_builder.tryBuildType("int*")); + ASSERT_TRUE(environment.hasType("int*")); +} + +TEST(TypeBuilder, true_doublePointer) { + filc::Environment environment; + const filc::TypeBuilder type_builder(&environment); + + ASSERT_FALSE(environment.hasType("int**")); + ASSERT_TRUE(type_builder.tryBuildType("int**")); + ASSERT_TRUE(environment.hasType("int**")); +} + +TEST(TypeBuilder, true_simpleArray) { + filc::Environment environment; + const filc::TypeBuilder type_builder(&environment); + + ASSERT_FALSE(environment.hasType("int[1]")); + ASSERT_TRUE(type_builder.tryBuildType("int[1]")); + ASSERT_TRUE(environment.hasType("int[1]")); +} + +TEST(TypeBuilder, true_doubleArray) { + filc::Environment environment; + const filc::TypeBuilder type_builder(&environment); + + ASSERT_FALSE(environment.hasType("int[1][2]")); + ASSERT_TRUE(type_builder.tryBuildType("int[1][2]")); + ASSERT_TRUE(environment.hasType("int[1][2]")); +} + +TEST(TypeBuilder, true_pointerArray) { + filc::Environment environment; + const filc::TypeBuilder type_builder(&environment); + + ASSERT_FALSE(environment.hasType("int*[1]")); + ASSERT_TRUE(type_builder.tryBuildType("int*[1]")); + ASSERT_TRUE(environment.hasType("int*[1]")); +} + +TEST(TypeBuilder, true_arrayPointer) { + filc::Environment environment; + const filc::TypeBuilder type_builder(&environment); + + ASSERT_FALSE(environment.hasType("int[1]*")); + ASSERT_TRUE(type_builder.tryBuildType("int[1]*")); + ASSERT_TRUE(environment.hasType("int[1]*")); +} diff --git a/tests/unit/validation/ValidationVisitorTest.cpp b/tests/unit/validation/ValidationVisitorTest.cpp index 1b2f0d9..88f6c31 100644 --- a/tests/unit/validation/ValidationVisitorTest.cpp +++ b/tests/unit/validation/ValidationVisitorTest.cpp @@ -416,3 +416,60 @@ TEST(ValidationVisitor, variableAddress_valid) { ASSERT_STREQ("i32*", program->getExpressions()[1]->getType()->getName().c_str()); ASSERT_STREQ("i32", program->getExpressions()[2]->getType()->getName().c_str()); } + +TEST(ValidationVisitor, array_differentType) { + VISITOR; + const auto program = parseString("[1, true];0"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT( + std::string(std::istreambuf_iterator(ss), {}), HasSubstr("All values of an array should be of the same type") + ); + ASSERT_TRUE(visitor.hasError()); +} + +TEST(ValidationVisitor, array_valid) { + VISITOR; + const auto program = parseString("val foo = [1, 2, 3];0"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("i32[3]", program->getExpressions()[0]->getType()->getName().c_str()); +} + +TEST(ValidationVisitor, array_validCast) { + VISITOR; + const auto program = parseString("val foo: i8[3] = [1, 2, 3];0"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("i8[3]", program->getExpressions()[0]->getType()->getName().c_str()); +} + +TEST(ValidationVisitor, array_invalidCast) { + VISITOR; + const auto program = parseString("val foo: u32 = [1, 2, 3]"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT( + std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("Cannot cast an array to a type not corresponding to an array: u32") + ); + ASSERT_TRUE(visitor.hasError()); +} + +TEST(ValidationVisitor, array_empty) { + VISITOR; + const auto program = parseString("[];0"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Value not used")); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("void[0]", program->getExpressions()[0]->getType()->getName().c_str()); +} + +TEST(ValidationVisitor, array_emptyCast) { + VISITOR; + const auto program = parseString("val foo: char[0] = [];0"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("char[0]", program->getExpressions()[0]->getType()->getDisplayName().c_str()); +} From 01c2d4a6277cc9842dda48744c899ece69767f5c Mon Sep 17 00:00:00 2001 From: Kevin Traini Date: Sat, 18 Jan 2025 10:58:42 +0100 Subject: [PATCH 2/5] feat: Introduce array access Part of #56 Array access with [] operator. Generate IR for both array initialization and access --- include/filc/grammar/DumpVisitor.h | 2 + include/filc/grammar/Type.h | 2 + include/filc/grammar/Visitor.h | 2 + include/filc/grammar/array/Array.h | 22 ++++++++ include/filc/grammar/ast.h | 2 + include/filc/llvm/IRGenerator.h | 2 + include/filc/validation/ValidationVisitor.h | 2 + src/grammar/DumpVisitor.cpp | 5 ++ src/grammar/FilParser.g4 | 8 +++ src/grammar/Type.cpp | 4 ++ src/grammar/array/ArrayAccess.cpp | 52 +++++++++++++++++++ src/llvm/IRGenerator.cpp | 25 +++++++-- src/validation/ValidationVisitor.cpp | 31 +++++++++++ tests/e2e/llvm_ir.cpp | 4 ++ tests/unit/grammar/array/ArrayAccessTest.cpp | 40 ++++++++++++++ tests/unit/llvm/IRGeneratorTest.cpp | 13 +++++ tests/unit/test_tools.cpp | 4 ++ tests/unit/test_tools.h | 2 + .../unit/validation/ValidationVisitorTest.cpp | 40 ++++++++++++++ 19 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 src/grammar/array/ArrayAccess.cpp create mode 100644 tests/unit/grammar/array/ArrayAccessTest.cpp diff --git a/include/filc/grammar/DumpVisitor.h b/include/filc/grammar/DumpVisitor.h index 638c05b..602f59f 100644 --- a/include/filc/grammar/DumpVisitor.h +++ b/include/filc/grammar/DumpVisitor.h @@ -60,6 +60,8 @@ class DumpVisitor final: public Visitor { auto visitArray(Array *array) -> void override; + auto visitArrayAccess(ArrayAccess *array) -> void override; + private: std::ostream &_out; int _indent_level; diff --git a/include/filc/grammar/Type.h b/include/filc/grammar/Type.h index ca75a55..2311435 100644 --- a/include/filc/grammar/Type.h +++ b/include/filc/grammar/Type.h @@ -96,6 +96,8 @@ class ArrayType final : public AbstractType { [[nodiscard]] auto toDisplay() const noexcept -> std::string override; + [[nodiscard]] auto getSize() const noexcept -> unsigned int; + [[nodiscard]] auto getContainedType() const noexcept -> std::shared_ptr; auto generateLLVMType(llvm::LLVMContext *context) -> void override; diff --git a/include/filc/grammar/Visitor.h b/include/filc/grammar/Visitor.h index 17dfe9b..4a3d3b4 100644 --- a/include/filc/grammar/Visitor.h +++ b/include/filc/grammar/Visitor.h @@ -61,6 +61,8 @@ template class Visitor { virtual auto visitArray(Array *array) -> Return = 0; + virtual auto visitArrayAccess(ArrayAccess *array) -> Return = 0; + protected: Visitor() = default; }; diff --git a/include/filc/grammar/array/Array.h b/include/filc/grammar/array/Array.h index 9670ed9..fe4a452 100644 --- a/include/filc/grammar/array/Array.h +++ b/include/filc/grammar/array/Array.h @@ -46,6 +46,28 @@ class Array final : public Expression { unsigned long _size; std::vector> _values; }; + +class ArrayAccess final : public Expression { + public: + ArrayAccess(std::string name, unsigned int index); + + [[nodiscard]] auto getName() const -> std::string; + + [[nodiscard]] auto getIndex() const -> unsigned int; + + auto setArrayType(const std::shared_ptr &array_type) -> void; + + [[nodiscard]] auto getArrayType() const -> const std::shared_ptr &; + + auto acceptVoidVisitor(Visitor *visitor) -> void override; + + auto acceptIRVisitor(Visitor *visitor) -> llvm::Value * override; + + private: + std::string _name; + unsigned int _index; + std::shared_ptr _array_type; +}; } // namespace filc #endif // FILC_ARRAY_H diff --git a/include/filc/grammar/ast.h b/include/filc/grammar/ast.h index 2374c5a..130c298 100644 --- a/include/filc/grammar/ast.h +++ b/include/filc/grammar/ast.h @@ -57,6 +57,8 @@ class PointerDereferencing; class VariableAddress; class Array; + +class ArrayAccess; } #endif // FILC_AST_H diff --git a/include/filc/llvm/IRGenerator.h b/include/filc/llvm/IRGenerator.h index 524df68..6c95e86 100644 --- a/include/filc/llvm/IRGenerator.h +++ b/include/filc/llvm/IRGenerator.h @@ -70,6 +70,8 @@ class IRGenerator final: public Visitor { auto visitArray(Array *array) -> llvm::Value * override; + auto visitArrayAccess(ArrayAccess *array) -> llvm::Value * override; + private: std::unique_ptr _llvm_context; std::unique_ptr _module; diff --git a/include/filc/validation/ValidationVisitor.h b/include/filc/validation/ValidationVisitor.h index f2775a0..8cf1233 100644 --- a/include/filc/validation/ValidationVisitor.h +++ b/include/filc/validation/ValidationVisitor.h @@ -100,6 +100,8 @@ class ValidationVisitor final : public Visitor { auto visitArray(Array *array) -> void override; + auto visitArrayAccess(ArrayAccess *array) -> void override; + private: std::unique_ptr _context; std::unique_ptr _environment; diff --git a/src/grammar/DumpVisitor.cpp b/src/grammar/DumpVisitor.cpp index df66676..f9a658a 100644 --- a/src/grammar/DumpVisitor.cpp +++ b/src/grammar/DumpVisitor.cpp @@ -175,6 +175,11 @@ auto DumpVisitor::visitArray(Array *array) -> void { _indent_level--; } +auto DumpVisitor::visitArrayAccess(ArrayAccess *array) -> void { + printIdent(); + _out << "[ArrayAccess:" << array->getName() << ":" << array->getIndex() << "]\n"; +} + auto DumpVisitor::printIdent() const -> void { _out << std::string(_indent_level, '\t'); } diff --git a/src/grammar/FilParser.g4 b/src/grammar/FilParser.g4 index 86e4d4c..90b1fde 100644 --- a/src/grammar/FilParser.g4 +++ b/src/grammar/FilParser.g4 @@ -62,6 +62,9 @@ expression returns[std::shared_ptr tree] | v=variable_declaration { $tree = $v.tree; } + | aa=array_access { + $tree = $aa.tree; + } | i=IDENTIFIER { $tree = std::make_shared($i.text); } @@ -206,3 +209,8 @@ array_values returns[std::vector> values] auto values_to_insert = $v.values; $values.insert($values.end(), values_to_insert.begin(), values_to_insert.end()); })?; + +array_access returns[std::shared_ptr tree] + : i=IDENTIFIER LBRACK n=INTEGER RBRACK { + $tree = std::make_shared($i.text, stoi($n.text)); + }; diff --git a/src/grammar/Type.cpp b/src/grammar/Type.cpp index e3f79b2..540194b 100644 --- a/src/grammar/Type.cpp +++ b/src/grammar/Type.cpp @@ -94,6 +94,10 @@ auto ArrayType::getDisplayName() const noexcept -> std::string { return _contained_type->getDisplayName() + "[" + std::to_string(_size) + "]"; } +auto ArrayType::getSize() const noexcept -> unsigned int { + return _size; +} + auto ArrayType::getContainedType() const noexcept -> std::shared_ptr { return _contained_type; } diff --git a/src/grammar/array/ArrayAccess.cpp b/src/grammar/array/ArrayAccess.cpp new file mode 100644 index 0000000..93016bf --- /dev/null +++ b/src/grammar/array/ArrayAccess.cpp @@ -0,0 +1,52 @@ +/** + * MIT License + * + * Copyright (c) 2025-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "filc/grammar/array/Array.h" + +using namespace filc; + +ArrayAccess::ArrayAccess(std::string name, const unsigned int index): _name(std::move(name)), _index(index) {} + +auto ArrayAccess::getName() const -> std::string { + return _name; +} + +auto ArrayAccess::getIndex() const -> unsigned int { + return _index; +} + +auto ArrayAccess::setArrayType(const std::shared_ptr &array_type) -> void { + _array_type = array_type; +} + +auto ArrayAccess::getArrayType() const -> const std::shared_ptr & { + return _array_type; +} + +auto ArrayAccess::acceptVoidVisitor(Visitor *visitor) -> void { + visitor->visitArrayAccess(this); +} + +auto ArrayAccess::acceptIRVisitor(Visitor *visitor) -> llvm::Value * { + return visitor->visitArrayAccess(this); +} diff --git a/src/llvm/IRGenerator.cpp b/src/llvm/IRGenerator.cpp index 66dbbef..43a8bd8 100644 --- a/src/llvm/IRGenerator.cpp +++ b/src/llvm/IRGenerator.cpp @@ -32,6 +32,7 @@ #include "filc/grammar/variable/Variable.h" #include "filc/llvm/CalculBuilder.h" +#include #include #include #include @@ -74,7 +75,9 @@ auto IRGenerator::toTarget(const std::string &output_file, const std::string &ta return 1; } const llvm::TargetOptions options; - const auto target_machine = target->createTargetMachine(used_target_triple, "", "", options, llvm::Reloc::PIC_); + const auto target_machine = target->createTargetMachine( + used_target_triple, "", "", options, llvm::Reloc::PIC_, std::nullopt, llvm::CodeGenOptLevel::None + ); _module->setDataLayout(target_machine->createDataLayout()); _module->setTargetTriple(used_target_triple); @@ -206,6 +209,22 @@ auto IRGenerator::visitVariableAddress(VariableAddress *address) -> llvm::Value } auto IRGenerator::visitArray(Array *array) -> llvm::Value * { - // TODO: alloc array size + add values, return pointer to first value - return nullptr; + const auto array_type = array->getType()->getLLVMType(_llvm_context.get()); + const auto alloca = _builder->CreateAlloca(array_type, array->getSize()); + const auto &array_values = array->getValues(); + for (unsigned int i = 0; i < array_values.size(); ++i) { + const auto llvm_value = array_values[i]->acceptIRVisitor(this); + const auto array_access = _builder->CreateConstInBoundsGEP2_64(array_type, alloca, 0, i); + _builder->CreateStore(llvm_value, array_access); + } + + return alloca; +} + +auto IRGenerator::visitArrayAccess(ArrayAccess *array) -> llvm::Value * { + const auto value = _context.getValue(array->getName()); + const auto gep = _builder->CreateConstInBoundsGEP2_64( + array->getArrayType()->getLLVMType(_llvm_context.get()), value, 0, array->getIndex() + ); + return _builder->CreateLoad(array->getType()->getLLVMType(_llvm_context.get()), gep); } diff --git a/src/validation/ValidationVisitor.cpp b/src/validation/ValidationVisitor.cpp index 1babcdc..c1eae52 100644 --- a/src/validation/ValidationVisitor.cpp +++ b/src/validation/ValidationVisitor.cpp @@ -435,3 +435,34 @@ auto ValidationVisitor::visitArray(Array *array) -> void { displayWarning("Value not used", array->getPosition()); } } + +auto ValidationVisitor::visitArrayAccess(ArrayAccess *array) -> void { + if (! _environment->hasName(array->getName())) { + displayError("Unknown name, don't know what it refers to: " + array->getName(), array->getPosition()); + return; + } + + const auto name = _environment->getName(array->getName()); + const auto type = std::dynamic_pointer_cast(name.getType()); + if (type == nullptr) { + displayError( + "Cannot access to offset on a variable of type " + name.getType()->toDisplay(), array->getPosition() + ); + return; + } + + if (array->getIndex() >= type->getSize()) { + displayError( + "Out of bound access to an array. Array has a size of " + std::to_string(type->getSize()), + array->getPosition() + ); + return; + } + + array->setArrayType(type); + array->setType(type->getContainedType()); + + if (! _context->has("return") || ! _context->get("return")) { + displayWarning("Value not used", array->getPosition()); + } +} diff --git a/tests/e2e/llvm_ir.cpp b/tests/e2e/llvm_ir.cpp index 1e2de1d..65f0468 100644 --- a/tests/e2e/llvm_ir.cpp +++ b/tests/e2e/llvm_ir.cpp @@ -64,3 +64,7 @@ TEST(ir_dump, pointer_program) { TEST(ir_dump, address_program) { ASSERT_EQ(4, getProgramResult("val foo = 4;val bar = &foo;*bar")); } + +TEST(ir_dump, array_program) { + ASSERT_EQ(2, getProgramResult("val foo = [1, 2, 3];foo[1]")); +} diff --git a/tests/unit/grammar/array/ArrayAccessTest.cpp b/tests/unit/grammar/array/ArrayAccessTest.cpp new file mode 100644 index 0000000..fae864c --- /dev/null +++ b/tests/unit/grammar/array/ArrayAccessTest.cpp @@ -0,0 +1,40 @@ +/** + * MIT License + * + * Copyright (c) 2025-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "test_tools.h" + +#include +#include +#include + +using namespace ::testing; + +TEST(ArrayAccess, parsing) { + const auto program = parseString("foo[12]"); + const auto expressions = program->getExpressions(); + ASSERT_THAT(expressions, SizeIs(1)); + const auto array_access = std::dynamic_pointer_cast(expressions[0]); + ASSERT_NE(nullptr, array_access); + ASSERT_STREQ("foo", array_access->getName().c_str()); + ASSERT_EQ(12, array_access->getIndex()); +} diff --git a/tests/unit/llvm/IRGeneratorTest.cpp b/tests/unit/llvm/IRGeneratorTest.cpp index 7802097..4300bc0 100644 --- a/tests/unit/llvm/IRGeneratorTest.cpp +++ b/tests/unit/llvm/IRGeneratorTest.cpp @@ -134,3 +134,16 @@ TEST(IRGenerator, variableAddress_notThrow) { ASSERT_THAT(ir, HasSubstr("store i32 0, ptr %0")); // bar = &foo ASSERT_THAT(ir, HasSubstr("ret i32 %1")); // Register %1 is *foo } + +TEST(IRGenerator, array_notThrow) { + const auto ir = getIR("[1, 2, 3];0"); + ASSERT_THAT(ir, HasSubstr("alloca [3 x i32]")); + ASSERT_THAT(ir, HasSubstr("store i32 1")); + ASSERT_THAT(ir, HasSubstr("store i32 2")); + ASSERT_THAT(ir, HasSubstr("store i32 3")); +} + +TEST(IRGenerator, arrayAccess_notThrow) { + const auto ir = getIR("val foo = [0];foo[0]"); + ASSERT_THAT(ir, HasSubstr("ret i32 %3")); +} diff --git a/tests/unit/test_tools.cpp b/tests/unit/test_tools.cpp index ba8e7f7..e95623f 100644 --- a/tests/unit/test_tools.cpp +++ b/tests/unit/test_tools.cpp @@ -144,6 +144,10 @@ auto PrinterVisitor::visitArray(filc::Array *array) -> void { _out << "]"; } +auto PrinterVisitor::visitArrayAccess(filc::ArrayAccess *array) -> void { + _out << array->getName() << "[" << array->getIndex() << "]"; +} + TokenSourceStub::TokenSourceStub(std::string filename): _filename(std::move(filename)) {} auto TokenSourceStub::nextToken() -> std::unique_ptr { diff --git a/tests/unit/test_tools.h b/tests/unit/test_tools.h index de7d636..55b52ed 100644 --- a/tests/unit/test_tools.h +++ b/tests/unit/test_tools.h @@ -72,6 +72,8 @@ class PrinterVisitor final: public filc::Visitor { auto visitArray(filc::Array *array) -> void override; + auto visitArrayAccess(filc::ArrayAccess *array) -> void override; + private: std::stringstream _out; }; diff --git a/tests/unit/validation/ValidationVisitorTest.cpp b/tests/unit/validation/ValidationVisitorTest.cpp index 88f6c31..b663982 100644 --- a/tests/unit/validation/ValidationVisitorTest.cpp +++ b/tests/unit/validation/ValidationVisitorTest.cpp @@ -473,3 +473,43 @@ TEST(ValidationVisitor, array_emptyCast) { ASSERT_FALSE(visitor.hasError()); ASSERT_STREQ("char[0]", program->getExpressions()[0]->getType()->getDisplayName().c_str()); } + +TEST(ValidationVisitor, arrayAccess_unknownName) { + VISITOR; + const auto program = parseString("foo[0]"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT( + std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Unknown name, don't know what it refers to: foo") + ); + ASSERT_TRUE(visitor.hasError()); +} + +TEST(ValidationVisitor, arrayAccess_notAnArray) { + VISITOR; + const auto program = parseString("val foo = 2;foo[0]"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT( + std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Cannot access to offset on a variable of type int") + ); + ASSERT_TRUE(visitor.hasError()); +} + +TEST(ValidationVisitor, arrayAccess_outOfBound) { + VISITOR; + const auto program = parseString("val foo = [25];foo[5]"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT( + std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("Out of bound access to an array. Array has a size of 1") + ); + ASSERT_TRUE(visitor.hasError()); +} + +TEST(ValidationVisitor, arrayAccess_valid) { + VISITOR; + const auto program = parseString("val foo = [1];foo[0]"); + program->acceptVoidVisitor(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("i32", program->getExpressions()[1]->getType()->getName().c_str()); +} From ac7097a8740eceb09513b35e6d454bb08e113441 Mon Sep 17 00:00:00 2001 From: Kevin Traini Date: Tue, 28 Jan 2025 18:20:37 +0100 Subject: [PATCH 3/5] feat: Allow to array_access on expression Part of #56 This needed to fix multi-dim array definition --- include/filc/grammar/DumpVisitor.h | 2 +- include/filc/grammar/Visitor.h | 35 ++++++++++++- include/filc/grammar/array/Array.h | 18 +++---- include/filc/llvm/IRGenerator.h | 3 +- include/filc/validation/ValidationVisitor.h | 35 +------------ src/grammar/DumpVisitor.cpp | 7 ++- src/grammar/FilParser.g4 | 9 +--- .../VisitorContext.cpp} | 20 ++++--- src/grammar/array/Array.cpp | 13 ++++- src/grammar/array/ArrayAccess.cpp | 15 ++---- src/llvm/IRGenerator.cpp | 52 ++++++++++++++----- src/validation/ValidationVisitor.cpp | 38 +++++++++----- tests/e2e/llvm_ir.cpp | 10 ++++ .../VisitorContextTest.cpp} | 47 ++++++++++------- tests/unit/grammar/array/ArrayAccessTest.cpp | 4 +- tests/unit/llvm/IRGeneratorTest.cpp | 17 +++++- tests/unit/test_tools.cpp | 5 +- tests/unit/test_tools.h | 2 +- .../unit/validation/ValidationVisitorTest.cpp | 11 ++++ 19 files changed, 218 insertions(+), 125 deletions(-) rename src/{validation/ValidationContext.cpp => grammar/VisitorContext.cpp} (74%) rename tests/unit/{validation/ValidationContextTest.cpp => grammar/VisitorContextTest.cpp} (73%) diff --git a/include/filc/grammar/DumpVisitor.h b/include/filc/grammar/DumpVisitor.h index 602f59f..9c0a428 100644 --- a/include/filc/grammar/DumpVisitor.h +++ b/include/filc/grammar/DumpVisitor.h @@ -60,7 +60,7 @@ class DumpVisitor final: public Visitor { auto visitArray(Array *array) -> void override; - auto visitArrayAccess(ArrayAccess *array) -> void override; + auto visitArrayAccess(ArrayAccess *array_access) -> void override; private: std::ostream &_out; diff --git a/include/filc/grammar/Visitor.h b/include/filc/grammar/Visitor.h index 4a3d3b4..2f10a8e 100644 --- a/include/filc/grammar/Visitor.h +++ b/include/filc/grammar/Visitor.h @@ -26,7 +26,11 @@ #include "filc/grammar/ast.h" +#include #include +#include +#include +#include namespace filc { template class Visitor { @@ -61,7 +65,7 @@ template class Visitor { virtual auto visitArray(Array *array) -> Return = 0; - virtual auto visitArrayAccess(ArrayAccess *array) -> Return = 0; + virtual auto visitArrayAccess(ArrayAccess *array_access) -> Return = 0; protected: Visitor() = default; @@ -75,6 +79,35 @@ class Visitable { virtual auto acceptIRVisitor(Visitor *visitor) -> llvm::Value * = 0; }; + +class VisitorContext final { + public: + VisitorContext(); + + auto stack() -> void; + + auto unstack() -> void; + + auto set(const std::string &key, const std::any &value) -> void; + + auto unset(const std::string &key) -> void; + + [[nodiscard]] auto has(const std::string &key) const -> bool; + + template + auto get(const std::string &key) const -> T { + if (_values.top().find(key) == _values.top().end()) { + throw std::logic_error("There is not value for key: " + key); + } + + return std::any_cast(_values.top().at(key)); + } + + auto clear() -> void; + + private: + std::stack> _values; +}; } // namespace filc #endif // FILC_VISITOR_H diff --git a/include/filc/grammar/array/Array.h b/include/filc/grammar/array/Array.h index fe4a452..66c7505 100644 --- a/include/filc/grammar/array/Array.h +++ b/include/filc/grammar/array/Array.h @@ -36,7 +36,11 @@ class Array final : public Expression { [[nodiscard]] auto getValues() const -> const std::vector> &; - [[nodiscard]] auto getSize() const -> unsigned int; + [[nodiscard]] auto getSize() const -> unsigned long; + + auto setFullSize(unsigned long full_size) -> void; + + [[nodiscard]] auto getFullSize() const -> unsigned long; auto acceptVoidVisitor(Visitor *visitor) -> void override; @@ -44,29 +48,25 @@ class Array final : public Expression { private: unsigned long _size; + unsigned long _full_size; std::vector> _values; }; class ArrayAccess final : public Expression { public: - ArrayAccess(std::string name, unsigned int index); + ArrayAccess(std::shared_ptr array, unsigned int index); - [[nodiscard]] auto getName() const -> std::string; + [[nodiscard]] auto getArray() const -> std::shared_ptr; [[nodiscard]] auto getIndex() const -> unsigned int; - auto setArrayType(const std::shared_ptr &array_type) -> void; - - [[nodiscard]] auto getArrayType() const -> const std::shared_ptr &; - auto acceptVoidVisitor(Visitor *visitor) -> void override; auto acceptIRVisitor(Visitor *visitor) -> llvm::Value * override; private: - std::string _name; + std::shared_ptr _array; unsigned int _index; - std::shared_ptr _array_type; }; } // namespace filc diff --git a/include/filc/llvm/IRGenerator.h b/include/filc/llvm/IRGenerator.h index 6c95e86..1220ec4 100644 --- a/include/filc/llvm/IRGenerator.h +++ b/include/filc/llvm/IRGenerator.h @@ -70,9 +70,10 @@ class IRGenerator final: public Visitor { auto visitArray(Array *array) -> llvm::Value * override; - auto visitArrayAccess(ArrayAccess *array) -> llvm::Value * override; + auto visitArrayAccess(ArrayAccess *array_access) -> llvm::Value * override; private: + std::unique_ptr _visitor_context; std::unique_ptr _llvm_context; std::unique_ptr _module; std::unique_ptr> _builder; diff --git a/include/filc/validation/ValidationVisitor.h b/include/filc/validation/ValidationVisitor.h index 8cf1233..0495d99 100644 --- a/include/filc/validation/ValidationVisitor.h +++ b/include/filc/validation/ValidationVisitor.h @@ -29,41 +29,10 @@ #include "filc/validation/Environment.h" #include "filc/validation/TypeBuilder.h" -#include -#include #include -#include -#include #include namespace filc { -class ValidationContext final { - public: - ValidationContext(); - - auto stack() -> void; - - auto unstack() -> void; - - auto set(const std::string &key, const std::any &value) -> void; - - [[nodiscard]] auto has(const std::string &key) const -> bool; - - template - auto get(const std::string &key) const -> T { - if (_values.top().find(key) == _values.top().end()) { - throw std::logic_error("There is not value for key: " + key); - } - - return std::any_cast(_values.top().at(key)); - } - - auto clear() -> void; - - private: - std::stack> _values; -}; - class ValidationVisitor final : public Visitor { public: explicit ValidationVisitor(std::ostream &out); @@ -100,10 +69,10 @@ class ValidationVisitor final : public Visitor { auto visitArray(Array *array) -> void override; - auto visitArrayAccess(ArrayAccess *array) -> void override; + auto visitArrayAccess(ArrayAccess *array_access) -> void override; private: - std::unique_ptr _context; + std::unique_ptr _context; std::unique_ptr _environment; TypeBuilder _type_builder; std::ostream &_out; diff --git a/src/grammar/DumpVisitor.cpp b/src/grammar/DumpVisitor.cpp index f9a658a..2a3d58d 100644 --- a/src/grammar/DumpVisitor.cpp +++ b/src/grammar/DumpVisitor.cpp @@ -175,9 +175,12 @@ auto DumpVisitor::visitArray(Array *array) -> void { _indent_level--; } -auto DumpVisitor::visitArrayAccess(ArrayAccess *array) -> void { +auto DumpVisitor::visitArrayAccess(ArrayAccess *array_access) -> void { printIdent(); - _out << "[ArrayAccess:" << array->getName() << ":" << array->getIndex() << "]\n"; + _out << "[ArrayAccess:" << array_access->getIndex() << "]\n"; + _indent_level++; + array_access->getArray()->acceptVoidVisitor(this); + _indent_level--; } auto DumpVisitor::printIdent() const -> void { diff --git a/src/grammar/FilParser.g4 b/src/grammar/FilParser.g4 index 90b1fde..789ed24 100644 --- a/src/grammar/FilParser.g4 +++ b/src/grammar/FilParser.g4 @@ -62,8 +62,8 @@ expression returns[std::shared_ptr tree] | v=variable_declaration { $tree = $v.tree; } - | aa=array_access { - $tree = $aa.tree; + | ea=expression LBRACK n=INTEGER RBRACK { + $tree = std::make_shared($ea.tree, stoi($n.text)); } | i=IDENTIFIER { $tree = std::make_shared($i.text); @@ -209,8 +209,3 @@ array_values returns[std::vector> values] auto values_to_insert = $v.values; $values.insert($values.end(), values_to_insert.begin(), values_to_insert.end()); })?; - -array_access returns[std::shared_ptr tree] - : i=IDENTIFIER LBRACK n=INTEGER RBRACK { - $tree = std::make_shared($i.text, stoi($n.text)); - }; diff --git a/src/validation/ValidationContext.cpp b/src/grammar/VisitorContext.cpp similarity index 74% rename from src/validation/ValidationContext.cpp rename to src/grammar/VisitorContext.cpp index 1ae9df5..92cfcc6 100644 --- a/src/validation/ValidationContext.cpp +++ b/src/grammar/VisitorContext.cpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2024-Present Kevin Traini + * Copyright (c) 2025-Present Kevin Traini * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,32 +21,36 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#include "filc/validation/ValidationVisitor.h" +#include "filc/grammar/Visitor.h" using namespace filc; -ValidationContext::ValidationContext() { +VisitorContext::VisitorContext() { stack(); } -auto ValidationContext::stack() -> void { +auto VisitorContext::stack() -> void { _values.emplace(); } -auto ValidationContext::unstack() -> void { +auto VisitorContext::unstack() -> void { if (_values.size() > 1) { _values.pop(); } } -auto ValidationContext::set(const std::string &key, const std::any &value) -> void { +auto VisitorContext::set(const std::string &key, const std::any &value) -> void { _values.top()[key] = value; } -auto ValidationContext::has(const std::string &key) const -> bool { +auto VisitorContext::unset(const std::string &key) -> void { + _values.top().erase(key); +} + +auto VisitorContext::has(const std::string &key) const -> bool { return _values.top().find(key) != _values.top().end(); } -auto ValidationContext::clear() -> void { +auto VisitorContext::clear() -> void { _values.top().clear(); } diff --git a/src/grammar/array/Array.cpp b/src/grammar/array/Array.cpp index f54e935..9ebda23 100644 --- a/src/grammar/array/Array.cpp +++ b/src/grammar/array/Array.cpp @@ -25,16 +25,25 @@ using namespace filc; -Array::Array(const std::vector> &values): _size(values.size()), _values(values) {} +Array::Array(const std::vector> &values) + : _size(values.size()), _full_size(0), _values(values) {} auto Array::getValues() const -> const std::vector> & { return _values; } -auto Array::getSize() const -> unsigned int { +auto Array::getSize() const -> unsigned long { return _size; } +auto Array::setFullSize(const unsigned long full_size) -> void { + _full_size = full_size; +} + +auto Array::getFullSize() const -> unsigned long { + return _full_size; +} + auto Array::acceptVoidVisitor(Visitor *visitor) -> void { visitor->visitArray(this); } diff --git a/src/grammar/array/ArrayAccess.cpp b/src/grammar/array/ArrayAccess.cpp index 93016bf..7e70e1a 100644 --- a/src/grammar/array/ArrayAccess.cpp +++ b/src/grammar/array/ArrayAccess.cpp @@ -25,24 +25,17 @@ using namespace filc; -ArrayAccess::ArrayAccess(std::string name, const unsigned int index): _name(std::move(name)), _index(index) {} +ArrayAccess::ArrayAccess(std::shared_ptr array, const unsigned int index) + : _array(std::move(array)), _index(index) {} -auto ArrayAccess::getName() const -> std::string { - return _name; +auto ArrayAccess::getArray() const -> std::shared_ptr { + return _array; } auto ArrayAccess::getIndex() const -> unsigned int { return _index; } -auto ArrayAccess::setArrayType(const std::shared_ptr &array_type) -> void { - _array_type = array_type; -} - -auto ArrayAccess::getArrayType() const -> const std::shared_ptr & { - return _array_type; -} - auto ArrayAccess::acceptVoidVisitor(Visitor *visitor) -> void { visitor->visitArrayAccess(this); } diff --git a/src/llvm/IRGenerator.cpp b/src/llvm/IRGenerator.cpp index 43a8bd8..c73c6c4 100644 --- a/src/llvm/IRGenerator.cpp +++ b/src/llvm/IRGenerator.cpp @@ -45,9 +45,10 @@ using namespace filc; IRGenerator::IRGenerator(const std::string &filename, const Environment *environment) { - _llvm_context = std::make_unique(); - _module = std::make_unique(llvm::StringRef(filename), *_llvm_context); - _builder = std::make_unique>(*_llvm_context); + _visitor_context = std::make_unique(); + _llvm_context = std::make_unique(); + _module = std::make_unique(llvm::StringRef(filename), *_llvm_context); + _builder = std::make_unique>(*_llvm_context); environment->prepareLLVMTypes(_llvm_context.get()); } @@ -209,22 +210,47 @@ auto IRGenerator::visitVariableAddress(VariableAddress *address) -> llvm::Value } auto IRGenerator::visitArray(Array *array) -> llvm::Value * { - const auto array_type = array->getType()->getLLVMType(_llvm_context.get()); - const auto alloca = _builder->CreateAlloca(array_type, array->getSize()); + const auto array_type = array->getType()->getLLVMType(_llvm_context.get()); + const auto in_array_def = _visitor_context->has("in_array_def"); + const auto alloca + = in_array_def + ? nullptr + : _builder->CreateAlloca( + array_type, llvm::ConstantInt::get(*_llvm_context, llvm::APInt(64, array->getFullSize(), false)) + ); const auto &array_values = array->getValues(); for (unsigned int i = 0; i < array_values.size(); ++i) { - const auto llvm_value = array_values[i]->acceptIRVisitor(this); - const auto array_access = _builder->CreateConstInBoundsGEP2_64(array_type, alloca, 0, i); - _builder->CreateStore(llvm_value, array_access); + const auto array_value = in_array_def ? _visitor_context->get("in_array_def") : alloca; + _visitor_context->stack(); + + const auto array_access = _builder->CreateConstInBoundsGEP2_64(array_type, array_value, 0, i); + _visitor_context->set("in_array_def", array_access); + const auto llvm_value = array_values[i]->acceptIRVisitor(this); + + if (! _visitor_context->has("was_in_array_def") || ! _visitor_context->get("was_in_array_def")) { + _builder->CreateStore(llvm_value, array_access); + } + + _visitor_context->unstack(); } + _visitor_context->set("was_in_array_def", true); + return alloca; } -auto IRGenerator::visitArrayAccess(ArrayAccess *array) -> llvm::Value * { - const auto value = _context.getValue(array->getName()); - const auto gep = _builder->CreateConstInBoundsGEP2_64( - array->getArrayType()->getLLVMType(_llvm_context.get()), value, 0, array->getIndex() +auto IRGenerator::visitArrayAccess(ArrayAccess *array_access) -> llvm::Value * { + _visitor_context->stack(); + _visitor_context->set("in_array_access", true); + const auto value = array_access->getArray()->acceptIRVisitor(this); + _visitor_context->unstack(); + const auto gep = _builder->CreateConstInBoundsGEP2_64( + array_access->getArray()->getType()->getLLVMType(_llvm_context.get()), value, 0, array_access->getIndex() ); - return _builder->CreateLoad(array->getType()->getLLVMType(_llvm_context.get()), gep); + + if (_visitor_context->has("in_array_access") && _visitor_context->get("in_array_access")) { + return gep; + } + + return _builder->CreateLoad(array_access->getType()->getLLVMType(_llvm_context.get()), gep); } diff --git a/src/validation/ValidationVisitor.cpp b/src/validation/ValidationVisitor.cpp index c1eae52..d55b54b 100644 --- a/src/validation/ValidationVisitor.cpp +++ b/src/validation/ValidationVisitor.cpp @@ -39,7 +39,7 @@ using namespace filc; ValidationVisitor::ValidationVisitor(std::ostream &out) - : _context(new ValidationContext()), _environment(new Environment()), _type_builder(_environment.get()), _out(out), + : _context(new VisitorContext()), _environment(new Environment()), _type_builder(_environment.get()), _out(out), _error(false) {} auto ValidationVisitor::getEnvironment() const -> const Environment * { @@ -378,6 +378,7 @@ auto ValidationVisitor::visitVariableAddress(VariableAddress *address) -> void { auto ValidationVisitor::visitArray(Array *array) -> void { if (array->getSize() == 0) { + array->setFullSize(0); if (_context->has("cast_type")) { array->setType(_context->get>("cast_type")); } else { @@ -404,6 +405,7 @@ auto ValidationVisitor::visitArray(Array *array) -> void { } std::vector> values_types; + unsigned long full_size = 0; for (const auto &value : array->getValues()) { value->acceptVoidVisitor(this); @@ -412,7 +414,15 @@ auto ValidationVisitor::visitArray(Array *array) -> void { return; } values_types.push_back(value_type); + + if (_context->has("array_size")) { + full_size += _context->get("array_size"); + _context->unset("array_size"); + } else { + full_size++; + } } + array->setFullSize(full_size); if (_context->has("cast_type")) { _context->unstack(); @@ -431,38 +441,40 @@ auto ValidationVisitor::visitArray(Array *array) -> void { array->setType(_environment->getType(type_name)); } + _context->set("array_size", array->getFullSize()); + if (! _context->has("return") || ! _context->get("return")) { displayWarning("Value not used", array->getPosition()); } } -auto ValidationVisitor::visitArrayAccess(ArrayAccess *array) -> void { - if (! _environment->hasName(array->getName())) { - displayError("Unknown name, don't know what it refers to: " + array->getName(), array->getPosition()); +auto ValidationVisitor::visitArrayAccess(ArrayAccess *array_access) -> void { + const auto array = array_access->getArray(); + array->acceptVoidVisitor(this); + + const auto array_type = array->getType(); + if (array_type == nullptr) { return; } - - const auto name = _environment->getName(array->getName()); - const auto type = std::dynamic_pointer_cast(name.getType()); + const auto type = std::dynamic_pointer_cast(array_type); if (type == nullptr) { displayError( - "Cannot access to offset on a variable of type " + name.getType()->toDisplay(), array->getPosition() + "Cannot access to offset on a variable of type " + array_type->toDisplay(), array_access->getPosition() ); return; } - if (array->getIndex() >= type->getSize()) { + if (array_access->getIndex() >= type->getSize()) { displayError( "Out of bound access to an array. Array has a size of " + std::to_string(type->getSize()), - array->getPosition() + array_access->getPosition() ); return; } - array->setArrayType(type); - array->setType(type->getContainedType()); + array_access->setType(type->getContainedType()); if (! _context->has("return") || ! _context->get("return")) { - displayWarning("Value not used", array->getPosition()); + displayWarning("Value not used", array_access->getPosition()); } } diff --git a/tests/e2e/llvm_ir.cpp b/tests/e2e/llvm_ir.cpp index 65f0468..4f6d5a1 100644 --- a/tests/e2e/llvm_ir.cpp +++ b/tests/e2e/llvm_ir.cpp @@ -67,4 +67,14 @@ TEST(ir_dump, address_program) { TEST(ir_dump, array_program) { ASSERT_EQ(2, getProgramResult("val foo = [1, 2, 3];foo[1]")); + ASSERT_EQ(6, getProgramResult("[4, 5, 6][2]")); +} + +TEST(ir_dump, multi_dim_array_program) { + ASSERT_EQ(5, getProgramResult("[[1, 2, 3], [4, 5, 6], [7, 8, 9]][1][1]")); + ASSERT_EQ( + 1, + getProgramResult("[[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [[9, 0, 1], [2, 3, 4], [5, 6, 7]], [[8, 9, 0], [1, 2, " + "3], [4, 5, 6]]][2][1][0]") + ); } diff --git a/tests/unit/validation/ValidationContextTest.cpp b/tests/unit/grammar/VisitorContextTest.cpp similarity index 73% rename from tests/unit/validation/ValidationContextTest.cpp rename to tests/unit/grammar/VisitorContextTest.cpp index 828666b..6210386 100644 --- a/tests/unit/validation/ValidationContextTest.cpp +++ b/tests/unit/grammar/VisitorContextTest.cpp @@ -21,14 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#include +#include #include #include using namespace ::testing; -TEST(ValidationContext, stack_unstack) { - filc::ValidationContext context; +TEST(VisitorContext, stack_unstack) { + filc::VisitorContext context; context.set("key", 3); ASSERT_TRUE(context.has("key")); context.stack(); @@ -38,21 +38,21 @@ TEST(ValidationContext, stack_unstack) { ASSERT_EQ(3, context.get("key")); } -TEST(ValidationContext, get_non_existing) { - filc::ValidationContext context; +TEST(VisitorContext, get_non_existing) { + const filc::VisitorContext context; ASSERT_FALSE(context.has("non-existing")); ASSERT_THROW(context.get("non-existing"), std::logic_error); } -TEST(ValidationContext, get_set_scalar) { - filc::ValidationContext context; +TEST(VisitorContext, get_set_scalar) { + filc::VisitorContext context; context.set("int_value", 2); ASSERT_TRUE(context.has("int_value")); ASSERT_EQ(2, context.get("int_value")); } -TEST(ValidationContext, get_set_string) { - filc::ValidationContext context; +TEST(VisitorContext, get_set_string) { + filc::VisitorContext context; context.set("string_value", std::string("Hello")); ASSERT_TRUE(context.has("string_value")); ASSERT_STREQ("Hello", context.get("string_value").c_str()); @@ -64,17 +64,17 @@ typedef struct { char _c[5]; } SomeStructure; -TEST(ValidationContext, get_set_structure) { - filc::ValidationContext context; +TEST(VisitorContext, get_set_structure) { + filc::VisitorContext context; SomeStructure value = { 2, "Hello World", {'a', 'b', 'c', 'd', 'e'} }; context.set("struct_value", value); ASSERT_TRUE(context.has("struct_value")); - auto found = context.get("struct_value"); - ASSERT_EQ(2, found._a); - ASSERT_STREQ("Hello World", found._b.c_str()); - ASSERT_THAT(found._c, ElementsAre('a', 'b', 'c', 'd', 'e')); + const auto [a, b, c] = context.get("struct_value"); + ASSERT_EQ(2, a); + ASSERT_STREQ("Hello World", b.c_str()); + ASSERT_THAT(c, ElementsAre('a', 'b', 'c', 'd', 'e')); } class SomeClass { @@ -90,18 +90,27 @@ class SomeClass { std::string _b; }; -TEST(ValidationContext, get_set_object) { - filc::ValidationContext context; +TEST(VisitorContext, get_set_object) { + filc::VisitorContext context; SomeClass value; context.set("object_value", value); ASSERT_TRUE(context.has("object_value")); ASSERT_TRUE(value.equals(context.get("object_value"))); } -TEST(ValidationContext, clear) { - filc::ValidationContext context; +TEST(VisitorContext, clear) { + filc::VisitorContext context; context.set("key", "value"); ASSERT_TRUE(context.has("key")); context.clear(); ASSERT_FALSE(context.has("key")); } + +TEST(VisitorContext, unset) { + filc::VisitorContext context; + ASSERT_FALSE(context.has("value")); + context.set("value", true); + ASSERT_TRUE(context.has("value")); + context.unset("value"); + ASSERT_FALSE(context.has("value")); +} diff --git a/tests/unit/grammar/array/ArrayAccessTest.cpp b/tests/unit/grammar/array/ArrayAccessTest.cpp index fae864c..0a26de4 100644 --- a/tests/unit/grammar/array/ArrayAccessTest.cpp +++ b/tests/unit/grammar/array/ArrayAccessTest.cpp @@ -24,6 +24,7 @@ #include "test_tools.h" #include +#include #include #include @@ -35,6 +36,7 @@ TEST(ArrayAccess, parsing) { ASSERT_THAT(expressions, SizeIs(1)); const auto array_access = std::dynamic_pointer_cast(expressions[0]); ASSERT_NE(nullptr, array_access); - ASSERT_STREQ("foo", array_access->getName().c_str()); + const auto identifier = std::dynamic_pointer_cast(array_access->getArray()); + ASSERT_STREQ("foo", identifier->getName().c_str()); ASSERT_EQ(12, array_access->getIndex()); } diff --git a/tests/unit/llvm/IRGeneratorTest.cpp b/tests/unit/llvm/IRGeneratorTest.cpp index 4300bc0..6c0820d 100644 --- a/tests/unit/llvm/IRGeneratorTest.cpp +++ b/tests/unit/llvm/IRGeneratorTest.cpp @@ -137,12 +137,27 @@ TEST(IRGenerator, variableAddress_notThrow) { TEST(IRGenerator, array_notThrow) { const auto ir = getIR("[1, 2, 3];0"); - ASSERT_THAT(ir, HasSubstr("alloca [3 x i32]")); + ASSERT_THAT(ir, HasSubstr("alloca [3 x i32], i64 3, align 4")); ASSERT_THAT(ir, HasSubstr("store i32 1")); ASSERT_THAT(ir, HasSubstr("store i32 2")); ASSERT_THAT(ir, HasSubstr("store i32 3")); } +TEST(IRGenerator, array_multiDimensions) { + const auto ir2D = getIR("[[1, 2], [3, 4]];0"); + ASSERT_THAT(ir2D, HasSubstr("alloca [2 x [2 x i32]], i64 4, align 4")); + + const auto ir3D = getIR( + "[" + " [[0, 1, 2], [3, 4, 5], [6, 7, 8]]," + " [[9, 0, 1], [2, 3, 4], [5, 6, 7]]," + " [[8, 9, 0], [1, 2, 3], [4, 5, 6]]" + "]" + ";0" + ); + ASSERT_THAT(ir3D, HasSubstr("alloca [3 x [3 x [3 x i32]]], i64 27, align 4")); +} + TEST(IRGenerator, arrayAccess_notThrow) { const auto ir = getIR("val foo = [0];foo[0]"); ASSERT_THAT(ir, HasSubstr("ret i32 %3")); diff --git a/tests/unit/test_tools.cpp b/tests/unit/test_tools.cpp index e95623f..1683549 100644 --- a/tests/unit/test_tools.cpp +++ b/tests/unit/test_tools.cpp @@ -144,8 +144,9 @@ auto PrinterVisitor::visitArray(filc::Array *array) -> void { _out << "]"; } -auto PrinterVisitor::visitArrayAccess(filc::ArrayAccess *array) -> void { - _out << array->getName() << "[" << array->getIndex() << "]"; +auto PrinterVisitor::visitArrayAccess(filc::ArrayAccess *array_access) -> void { + array_access->getArray()->acceptVoidVisitor(this); + _out << "[" << array_access->getIndex() << "]"; } TokenSourceStub::TokenSourceStub(std::string filename): _filename(std::move(filename)) {} diff --git a/tests/unit/test_tools.h b/tests/unit/test_tools.h index 55b52ed..31e06f4 100644 --- a/tests/unit/test_tools.h +++ b/tests/unit/test_tools.h @@ -72,7 +72,7 @@ class PrinterVisitor final: public filc::Visitor { auto visitArray(filc::Array *array) -> void override; - auto visitArrayAccess(filc::ArrayAccess *array) -> void override; + auto visitArrayAccess(filc::ArrayAccess *array_access) -> void override; private: std::stringstream _out; diff --git a/tests/unit/validation/ValidationVisitorTest.cpp b/tests/unit/validation/ValidationVisitorTest.cpp index b663982..8f73d3f 100644 --- a/tests/unit/validation/ValidationVisitorTest.cpp +++ b/tests/unit/validation/ValidationVisitorTest.cpp @@ -23,6 +23,7 @@ */ #include "test_tools.h" +#include #include #include #include @@ -436,6 +437,16 @@ TEST(ValidationVisitor, array_valid) { ASSERT_STREQ("i32[3]", program->getExpressions()[0]->getType()->getName().c_str()); } +TEST(ValidationVisitor, array_validFullSize) { + VISITOR; + const auto program = parseString("[[1, 2], [3, 4]];0"); + program->acceptVoidVisitor(&visitor); + ASSERT_FALSE(visitor.hasError()); + const auto array = std::dynamic_pointer_cast(program->getExpressions()[0]); + ASSERT_STREQ("i32[2][2]", array->getType()->getName().c_str()); + ASSERT_EQ(4, array->getFullSize()); +} + TEST(ValidationVisitor, array_validCast) { VISITOR; const auto program = parseString("val foo: i8[3] = [1, 2, 3];0"); From d437f2fdb3da4d0431e21f319a9386782b01901a Mon Sep 17 00:00:00 2001 From: Kevin Traini Date: Tue, 28 Jan 2025 21:19:22 +0100 Subject: [PATCH 4/5] feat: Allow * and & on expressions Part of #56 Allow usage of pointer dereferencing and variable address on expression and not just identifier --- include/filc/grammar/pointer/Pointer.h | 12 ++++----- src/grammar/DumpVisitor.cpp | 10 ++++++-- src/grammar/FilParser.g4 | 8 +++--- src/grammar/pointer/PointerDereferencing.cpp | 8 +++--- src/grammar/pointer/VariableAddress.cpp | 8 +++--- src/llvm/IRGenerator.cpp | 10 +++----- src/validation/ValidationVisitor.cpp | 25 +++++++++++-------- tests/e2e/llvm_ir.cpp | 4 +++ tests/unit/grammar/DumpVisitorTest.cpp | 10 +++++--- .../pointer/PointerDereferencingTest.cpp | 5 +++- .../grammar/pointer/VariableAddressTest.cpp | 5 +++- tests/unit/llvm/IRGeneratorTest.cpp | 2 +- tests/unit/test_tools.cpp | 6 +++-- .../unit/validation/ValidationVisitorTest.cpp | 20 --------------- 14 files changed, 65 insertions(+), 68 deletions(-) diff --git a/include/filc/grammar/pointer/Pointer.h b/include/filc/grammar/pointer/Pointer.h index 553dbdb..bf43b08 100644 --- a/include/filc/grammar/pointer/Pointer.h +++ b/include/filc/grammar/pointer/Pointer.h @@ -51,30 +51,30 @@ class Pointer final : public Expression { class PointerDereferencing final : public Expression { public: - explicit PointerDereferencing(std::string name); + explicit PointerDereferencing(const std::shared_ptr &pointer); - [[nodiscard]] auto getName() const -> std::string; + [[nodiscard]] auto getPointer() const -> std::shared_ptr; auto acceptVoidVisitor(Visitor *visitor) -> void override; auto acceptIRVisitor(Visitor *visitor) -> llvm::Value * override; private: - std::string _name; + std::shared_ptr _pointer; }; class VariableAddress final : public Expression { public: - explicit VariableAddress(std::string name); + explicit VariableAddress(const std::shared_ptr &variable); - [[nodiscard]] auto getName() const -> std::string; + [[nodiscard]] auto getVariable() const -> std::shared_ptr; auto acceptVoidVisitor(Visitor *visitor) -> void override; auto acceptIRVisitor(Visitor *visitor) -> llvm::Value * override; private: - std::string _name; + std::shared_ptr _variable; }; } // namespace filc diff --git a/src/grammar/DumpVisitor.cpp b/src/grammar/DumpVisitor.cpp index 2a3d58d..3a5311b 100644 --- a/src/grammar/DumpVisitor.cpp +++ b/src/grammar/DumpVisitor.cpp @@ -157,12 +157,18 @@ auto DumpVisitor::visitPointer(Pointer *pointer) -> void { auto DumpVisitor::visitPointerDereferencing(PointerDereferencing *pointer) -> void { printIdent(); - _out << "[PointerDereferencing:" << pointer->getName() << "]\n"; + _out << "[PointerDereferencing]\n"; + _indent_level++; + pointer->getPointer()->acceptVoidVisitor(this); + _indent_level--; } auto DumpVisitor::visitVariableAddress(VariableAddress *address) -> void { printIdent(); - _out << "[VariableAddress:" << address->getName() << "]\n"; + _out << "[VariableAddress]\n"; + _indent_level++; + address->getVariable()->acceptVoidVisitor(this); + _indent_level--; } auto DumpVisitor::visitArray(Array *array) -> void { diff --git a/src/grammar/FilParser.g4 b/src/grammar/FilParser.g4 index 789ed24..b12ca50 100644 --- a/src/grammar/FilParser.g4 +++ b/src/grammar/FilParser.g4 @@ -181,11 +181,11 @@ pointer returns[std::shared_ptr tree] }; pointer_operation returns[std::shared_ptr tree] - : STAR i=IDENTIFIER { - $tree = std::make_shared($i.text); + : STAR e=expression { + $tree = std::make_shared($e.tree); } - | AMP i=IDENTIFIER { - $tree = std::make_shared($i.text); + | AMP e=expression { + $tree = std::make_shared($e.tree); }; array returns[std::shared_ptr tree] diff --git a/src/grammar/pointer/PointerDereferencing.cpp b/src/grammar/pointer/PointerDereferencing.cpp index c60152e..4da8798 100644 --- a/src/grammar/pointer/PointerDereferencing.cpp +++ b/src/grammar/pointer/PointerDereferencing.cpp @@ -23,14 +23,12 @@ */ #include "filc/grammar/pointer/Pointer.h" -#include - using namespace filc; -PointerDereferencing::PointerDereferencing(std::string name): _name(std::move(name)) {} +PointerDereferencing::PointerDereferencing(const std::shared_ptr &pointer): _pointer(pointer) {} -auto PointerDereferencing::getName() const -> std::string { - return _name; +auto PointerDereferencing::getPointer() const -> std::shared_ptr { + return _pointer; } auto PointerDereferencing::acceptVoidVisitor(Visitor *visitor) -> void { diff --git a/src/grammar/pointer/VariableAddress.cpp b/src/grammar/pointer/VariableAddress.cpp index 8627485..9f3c7e0 100644 --- a/src/grammar/pointer/VariableAddress.cpp +++ b/src/grammar/pointer/VariableAddress.cpp @@ -23,14 +23,12 @@ */ #include "filc/grammar/pointer/Pointer.h" -#include - using namespace filc; -VariableAddress::VariableAddress(std::string name): _name(std::move(name)) {} +VariableAddress::VariableAddress(const std::shared_ptr &variable): _variable(variable) {} -auto VariableAddress::getName() const -> std::string { - return _name; +auto VariableAddress::getVariable() const -> std::shared_ptr { + return _variable; } auto VariableAddress::acceptVoidVisitor(Visitor *visitor) -> void { diff --git a/src/llvm/IRGenerator.cpp b/src/llvm/IRGenerator.cpp index c73c6c4..877de8c 100644 --- a/src/llvm/IRGenerator.cpp +++ b/src/llvm/IRGenerator.cpp @@ -193,17 +193,13 @@ auto IRGenerator::visitPointer(Pointer *pointer) -> llvm::Value * { } auto IRGenerator::visitPointerDereferencing(PointerDereferencing *pointer) -> llvm::Value * { - const auto pointer_value = _context.getValue(pointer->getName()); - if (pointer_value == nullptr) { - throw std::logic_error("Tried to access to a variable without a value set"); - } - + const auto pointer_value = pointer->getPointer()->acceptIRVisitor(this); return _builder->CreateLoad(pointer->getType()->getLLVMType(_llvm_context.get()), pointer_value); } auto IRGenerator::visitVariableAddress(VariableAddress *address) -> llvm::Value * { - const auto value = _context.getValue(address->getName()); - const auto alloca = _builder->CreateAlloca(value->getType()); + const auto value = address->getVariable()->acceptIRVisitor(this); + const auto alloca = _builder->CreateAlloca(address->getType()->getLLVMType(_llvm_context.get())); _builder->CreateStore(value, alloca); return alloca; diff --git a/src/validation/ValidationVisitor.cpp b/src/validation/ValidationVisitor.cpp index d55b54b..6553e73 100644 --- a/src/validation/ValidationVisitor.cpp +++ b/src/validation/ValidationVisitor.cpp @@ -334,13 +334,16 @@ auto ValidationVisitor::visitPointer(Pointer *pointer) -> void { } auto ValidationVisitor::visitPointerDereferencing(PointerDereferencing *pointer) -> void { - if (! _environment->hasName(pointer->getName())) { - displayError("Unknown name, don't know what it refers to: " + pointer->getName(), pointer->getPosition()); + _context->stack(); + _context->set("return", true); + pointer->getPointer()->acceptVoidVisitor(this); + _context->unstack(); + + const auto pointer_type = pointer->getPointer()->getType(); + if (pointer_type == nullptr) { return; } - - const auto name = _environment->getName(pointer->getName()); - const auto type = std::dynamic_pointer_cast(name.getType()); + const auto type = std::dynamic_pointer_cast(pointer_type); if (type == nullptr) { displayError("Cannot dereference a variable which is not a pointer", pointer->getPosition()); return; @@ -354,13 +357,15 @@ auto ValidationVisitor::visitPointerDereferencing(PointerDereferencing *pointer) } auto ValidationVisitor::visitVariableAddress(VariableAddress *address) -> void { - if (! _environment->hasName(address->getName())) { - displayError("Unknown name, don't know what it refers to: " + address->getName(), address->getPosition()); + _context->stack(); + _context->set("return", true); + address->getVariable()->acceptVoidVisitor(this); + _context->unstack(); + + const auto pointed_type = address->getVariable()->getType(); + if (pointed_type == nullptr) { return; } - - const auto name = _environment->getName(address->getName()); - const auto pointed_type = name.getType(); std::shared_ptr type = nullptr; if (_environment->hasType(pointed_type->getName() + "*")) { type = _environment->getType(pointed_type->getName() + "*"); diff --git a/tests/e2e/llvm_ir.cpp b/tests/e2e/llvm_ir.cpp index 4f6d5a1..cb5f1bc 100644 --- a/tests/e2e/llvm_ir.cpp +++ b/tests/e2e/llvm_ir.cpp @@ -65,6 +65,10 @@ TEST(ir_dump, address_program) { ASSERT_EQ(4, getProgramResult("val foo = 4;val bar = &foo;*bar")); } +TEST(ir_dump, pointer_address_program) { + ASSERT_EQ(3, getProgramResult("val foo = new i32(3);**&*&foo")); +} + TEST(ir_dump, array_program) { ASSERT_EQ(2, getProgramResult("val foo = [1, 2, 3];foo[1]")); ASSERT_EQ(6, getProgramResult("[4, 5, 6][2]")); diff --git a/tests/unit/grammar/DumpVisitorTest.cpp b/tests/unit/grammar/DumpVisitorTest.cpp index 415e300..63a7158 100644 --- a/tests/unit/grammar/DumpVisitorTest.cpp +++ b/tests/unit/grammar/DumpVisitorTest.cpp @@ -195,12 +195,14 @@ TEST(DumpVisitor, Pointer) { TEST(DumpVisitor, PointerDereferencing) { const auto dump = dumpProgram("*foo"); - ASSERT_THAT(dump, SizeIs(1)); - ASSERT_STREQ("[PointerDereferencing:foo]", dump[0].c_str()); + ASSERT_THAT(dump, SizeIs(2)); + ASSERT_STREQ("[PointerDereferencing]", dump[0].c_str()); + ASSERT_STREQ("\t[Identifier:foo]", dump[1].c_str()); } TEST(DumpVisitor, VariableAddress) { const auto dump = dumpProgram("&foo"); - ASSERT_THAT(dump, SizeIs(1)); - ASSERT_STREQ("[VariableAddress:foo]", dump[0].c_str()); + ASSERT_THAT(dump, SizeIs(2)); + ASSERT_STREQ("[VariableAddress]", dump[0].c_str()); + ASSERT_STREQ("\t[Identifier:foo]", dump[1].c_str()); } diff --git a/tests/unit/grammar/pointer/PointerDereferencingTest.cpp b/tests/unit/grammar/pointer/PointerDereferencingTest.cpp index 74599c1..5895299 100644 --- a/tests/unit/grammar/pointer/PointerDereferencingTest.cpp +++ b/tests/unit/grammar/pointer/PointerDereferencingTest.cpp @@ -23,6 +23,7 @@ */ #include "test_tools.h" +#include #include #include #include @@ -35,5 +36,7 @@ TEST(PointerDereferencing, parsing) { ASSERT_THAT(expressions, SizeIs(1)); const auto pointer_dereferencing = std::dynamic_pointer_cast(expressions[0]); ASSERT_NE(nullptr, pointer_dereferencing); - ASSERT_STREQ("foo", pointer_dereferencing->getName().c_str()); + const auto pointer = std::dynamic_pointer_cast(pointer_dereferencing->getPointer()); + ASSERT_NE(nullptr, pointer_dereferencing); + ASSERT_STREQ("foo", pointer->getName().c_str()); } diff --git a/tests/unit/grammar/pointer/VariableAddressTest.cpp b/tests/unit/grammar/pointer/VariableAddressTest.cpp index c2c6e49..af97dbd 100644 --- a/tests/unit/grammar/pointer/VariableAddressTest.cpp +++ b/tests/unit/grammar/pointer/VariableAddressTest.cpp @@ -23,6 +23,7 @@ */ #include "test_tools.h" +#include #include #include #include @@ -35,5 +36,7 @@ TEST(VariableAddress, parsing) { ASSERT_THAT(expressions, SizeIs(1)); const auto variable_address = std::dynamic_pointer_cast(expressions[0]); ASSERT_NE(nullptr, variable_address); - ASSERT_STREQ("foo", variable_address->getName().c_str()); + const auto variable = std::dynamic_pointer_cast(variable_address->getVariable()); + ASSERT_NE(nullptr, variable); + ASSERT_STREQ("foo", variable->getName().c_str()); } diff --git a/tests/unit/llvm/IRGeneratorTest.cpp b/tests/unit/llvm/IRGeneratorTest.cpp index 6c0820d..47156df 100644 --- a/tests/unit/llvm/IRGeneratorTest.cpp +++ b/tests/unit/llvm/IRGeneratorTest.cpp @@ -130,7 +130,7 @@ TEST(IRGenerator, pointerDereferencing_notThrow) { TEST(IRGenerator, variableAddress_notThrow) { const auto ir = getIR("val foo = 0;val bar = &foo;*bar"); - ASSERT_THAT(ir, HasSubstr("%0 = alloca i32")); + ASSERT_THAT(ir, HasSubstr("%0 = alloca ptr")); ASSERT_THAT(ir, HasSubstr("store i32 0, ptr %0")); // bar = &foo ASSERT_THAT(ir, HasSubstr("ret i32 %1")); // Register %1 is *foo } diff --git a/tests/unit/test_tools.cpp b/tests/unit/test_tools.cpp index 1683549..a530b78 100644 --- a/tests/unit/test_tools.cpp +++ b/tests/unit/test_tools.cpp @@ -128,11 +128,13 @@ auto PrinterVisitor::visitPointer(filc::Pointer *pointer) -> void { } auto PrinterVisitor::visitPointerDereferencing(filc::PointerDereferencing *pointer) -> void { - _out << "*" << pointer->getName(); + _out << "*"; + pointer->getPointer()->acceptVoidVisitor(this); } auto PrinterVisitor::visitVariableAddress(filc::VariableAddress *address) -> void { - _out << "&" << address->getName(); + _out << "&"; + address->getVariable()->acceptVoidVisitor(this); } auto PrinterVisitor::visitArray(filc::Array *array) -> void { diff --git a/tests/unit/validation/ValidationVisitorTest.cpp b/tests/unit/validation/ValidationVisitorTest.cpp index 8f73d3f..d8a787c 100644 --- a/tests/unit/validation/ValidationVisitorTest.cpp +++ b/tests/unit/validation/ValidationVisitorTest.cpp @@ -369,16 +369,6 @@ TEST(ValidationVisitor, pointer_valid) { ASSERT_STREQ("i32*", program->getExpressions()[0]->getType()->getName().c_str()); } -TEST(ValidationVisitor, pointerDereferencing_unknown) { - VISITOR; - const auto program = parseString("*foo"); - program->acceptVoidVisitor(&visitor); - ASSERT_THAT( - std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Unknown name, don't know what it refers to: foo") - ); - ASSERT_TRUE(visitor.hasError()); -} - TEST(ValidationVisitor, pointerDereferencing_notAPointer) { VISITOR; const auto program = parseString("val foo = 'a';*foo"); @@ -398,16 +388,6 @@ TEST(ValidationVisitor, pointerDereferencing_valid) { ASSERT_STREQ("i32", program->getExpressions()[1]->getType()->getName().c_str()); } -TEST(ValidationVisitor, variableAddress_unknown) { - VISITOR; - const auto program = parseString("&foo"); - program->acceptVoidVisitor(&visitor); - ASSERT_THAT( - std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Unknown name, don't know what it refers to: foo") - ); - ASSERT_TRUE(visitor.hasError()); -} - TEST(ValidationVisitor, variableAddress_valid) { VISITOR; const auto program = parseString("val foo = 2;val bar = &foo;*bar"); From 9c7a15de5387acf4beac2741a46d7481171f8ec5 Mon Sep 17 00:00:00 2001 From: Kevin Traini Date: Sat, 8 Feb 2025 09:58:28 +0100 Subject: [PATCH 5/5] feat: Add basic pointer arithmetic Part of #56 You can now do `pointer + number` --- include/filc/llvm/IRGenerator.h | 2 + include/filc/validation/CalculValidator.h | 20 ++++++--- src/llvm/CalculBuilder.cpp | 15 +++++++ src/validation/CalculValidator.cpp | 41 ++++++++++++++----- tests/e2e/llvm_ir.cpp | 4 ++ tests/unit/validation/CalculValidatorTest.cpp | 7 ++++ 6 files changed, 74 insertions(+), 15 deletions(-) diff --git a/include/filc/llvm/IRGenerator.h b/include/filc/llvm/IRGenerator.h index 1220ec4..fe611ae 100644 --- a/include/filc/llvm/IRGenerator.h +++ b/include/filc/llvm/IRGenerator.h @@ -33,6 +33,8 @@ namespace filc { class IRGenerator final: public Visitor { + friend class CalculBuilder; + public: explicit IRGenerator(const std::string &filename, const Environment *environment); diff --git a/include/filc/validation/CalculValidator.h b/include/filc/validation/CalculValidator.h index 144404e..4b427c7 100644 --- a/include/filc/validation/CalculValidator.h +++ b/include/filc/validation/CalculValidator.h @@ -26,6 +26,7 @@ #include "filc/grammar/Type.h" #include "filc/validation/Environment.h" + #include #include @@ -34,18 +35,27 @@ class CalculValidator { public: explicit CalculValidator(Environment *environment); - [[nodiscard]] auto isCalculValid(const std::shared_ptr &left_type, const std::string &op, - const std::shared_ptr &right_type) const -> std::shared_ptr; + [[nodiscard]] auto isCalculValid( + const std::shared_ptr &left_type, + const std::string &op, + const std::shared_ptr &right_type + ) const -> std::shared_ptr; private: Environment *_environment; - [[nodiscard]] auto isNumericOperatorValid(const std::shared_ptr &left_type, const std::string &op) const -> std::shared_ptr; + [[nodiscard]] auto + isNumericOperatorValid(const std::shared_ptr &left_type, const std::string &op) const + -> std::shared_ptr; [[nodiscard]] auto isBoolOperatorValid(const std::string &op) const -> std::shared_ptr; - [[nodiscard]] auto isPointerOperatorValid(const std::string &op) const -> std::shared_ptr; + [[nodiscard]] auto isPointerOperatorValid( + const std::string &op, + const std::shared_ptr &left_type, + const std::shared_ptr &right_type + ) const -> std::shared_ptr; }; -} +} // namespace filc #endif // FILC_CALCULVALIDATOR_H diff --git a/src/llvm/CalculBuilder.cpp b/src/llvm/CalculBuilder.cpp index 7fb145d..326e29e 100644 --- a/src/llvm/CalculBuilder.cpp +++ b/src/llvm/CalculBuilder.cpp @@ -354,6 +354,21 @@ auto CalculBuilder::buildPointer(const BinaryCalcul *calcul) const -> llvm::Valu "pointer_inequality" ); } + if (operation == "+") { + const auto left_type = calcul->getLeftExpression()->getType(); + const auto left_pointer_type = std::dynamic_pointer_cast(left_type); + if (left_pointer_type == nullptr) { + throw std::logic_error("Left operand of 'pointer +' is not a pointer"); + } + + const auto add = _builder->CreateGEP( + left_pointer_type->getPointedType()->getLLVMType(_generator->_llvm_context.get()), + calcul->getLeftExpression()->acceptIRVisitor(_generator), + calcul->getRightExpression()->acceptIRVisitor(_generator), + "pointer_add" + ); + return add; + } throw buildError(calcul); } diff --git a/src/validation/CalculValidator.cpp b/src/validation/CalculValidator.cpp index eceef91..6ef08b5 100644 --- a/src/validation/CalculValidator.cpp +++ b/src/validation/CalculValidator.cpp @@ -35,10 +35,8 @@ auto CalculValidator::isCalculValid( const std::string &op, const std::shared_ptr &right_type ) const -> std::shared_ptr { - if (left_type != right_type) { - return nullptr; - } - const auto type = left_type->getName(); + const auto left_name = left_type->getName(); + const auto right_name = right_type->getName(); const std::vector numeric_type = { "i8", @@ -54,16 +52,17 @@ auto CalculValidator::isCalculValid( "f32", "f64", }; - if (std::find(numeric_type.begin(), numeric_type.end(), type) != numeric_type.end()) { + if (std::find(numeric_type.begin(), numeric_type.end(), left_name) != numeric_type.end() + && left_type == right_type) { return isNumericOperatorValid(left_type, op); } - if (type == "bool") { + if (left_name == "bool" && left_type == right_type) { return isBoolOperatorValid(op); } - if (type[type.length() - 1] == '*') { // A pointer - return isPointerOperatorValid(op); + if (left_name[left_name.length() - 1] == '*') { // A pointer + return isPointerOperatorValid(op, left_type, right_type); } // We don't know what it is, so we assert it cannot be done @@ -94,10 +93,32 @@ auto CalculValidator::isBoolOperatorValid(const std::string &op) const -> std::s return nullptr; } -auto CalculValidator::isPointerOperatorValid(const std::string &op) const -> std::shared_ptr { - if (op == "==" || op == "!=") { +auto CalculValidator::isPointerOperatorValid( + const std::string &op, + const std::shared_ptr &left_type, + const std::shared_ptr &right_type +) const -> std::shared_ptr { + if ((op == "==" || op == "!=") && left_type == right_type) { return _environment->getType("bool"); } + if (op == "+") { + const std::vector integer_type = { + "i8", + "i16", + "i32", + "i64", + "i128", + "u8", + "u16", + "u32", + "u64", + "u128", + }; + if (std::find(integer_type.begin(), integer_type.end(), right_type->getName()) != integer_type.end()) { + return left_type; + } + } + return nullptr; } diff --git a/tests/e2e/llvm_ir.cpp b/tests/e2e/llvm_ir.cpp index cb5f1bc..4ecc1ce 100644 --- a/tests/e2e/llvm_ir.cpp +++ b/tests/e2e/llvm_ir.cpp @@ -69,6 +69,10 @@ TEST(ir_dump, pointer_address_program) { ASSERT_EQ(3, getProgramResult("val foo = new i32(3);**&*&foo")); } +TEST(ir_dump, pointer_arithmetic) { + ASSERT_EQ(3, getProgramResult("val foo = new i32(2);val bar = new i32(3);*(foo + 1)")); +} + TEST(ir_dump, array_program) { ASSERT_EQ(2, getProgramResult("val foo = [1, 2, 3];foo[1]")); ASSERT_EQ(6, getProgramResult("[4, 5, 6][2]")); diff --git a/tests/unit/validation/CalculValidatorTest.cpp b/tests/unit/validation/CalculValidatorTest.cpp index 345177f..c6500bf 100644 --- a/tests/unit/validation/CalculValidatorTest.cpp +++ b/tests/unit/validation/CalculValidatorTest.cpp @@ -63,6 +63,13 @@ TEST(CalculValidator, validPointer) { ->getName() .c_str() ); + + ASSERT_STREQ( + "bool*", + validator.isCalculValid(std::make_shared(env->getType("bool")), "+", env->getType("i32")) + ->getName() + .c_str() + ); } TEST(CalculValidator, invalidUnknown) {