diff --git a/example.elna b/example.elna index 3d04501..40e6ff2 100644 --- a/example.elna +++ b/example.elna @@ -103,6 +103,14 @@ begin writei("Test not false") end; +proc test_param(d: Int, e: Int); +begin + writei(""); + writei("Test param"); + writei(d); + writei(e) +end; + begin test_primitive(); test_string(); @@ -111,5 +119,6 @@ begin test_record(); test_const(); test_if(); - test_not() + test_not(); + test_param(8, 7) end. diff --git a/gcc/elna-generic.cc b/gcc/elna-generic.cc index 0cfbf47..14fecd0 100644 --- a/gcc/elna-generic.cc +++ b/gcc/elna-generic.cc @@ -87,10 +87,17 @@ namespace gcc } else if (symbol) { - tree fndecl_type = build_function_type_list(integer_type_node, NULL_TREE); + tree fndecl_type = build_function_type(void_type_node, TYPE_ARG_TYPES(symbol->payload)); tree printf_fn = build1(ADDR_EXPR, build_pointer_type(fndecl_type), symbol->payload); - tree stmt = build_call_nary(integer_type_node, printf_fn, 0); + std::vector arguments(statement->arguments().size()); + for (std::size_t i = 0; i < statement->arguments().size(); ++i) + { + statement->arguments().at(i)->accept(this); + arguments[i] = this->current_expression; + } + tree stmt = build_call_array_loc(get_location(&statement->position()), + void_type_node, printf_fn, arguments.size(), arguments.data()); append_to_statement_list(stmt, &this->current_statements); this->current_expression = NULL_TREE; @@ -148,14 +155,14 @@ namespace gcc void generic_visitor::visit(source::procedure_definition *definition) { - tree *parameter_types = reinterpret_cast(xmalloc(definition->parameters().size() * sizeof(tree))); + std::vector parameter_types(definition->parameters().size()); for (std::size_t i = 0; i < definition->parameters().size(); ++i) { parameter_types[i] = build_type(definition->parameters().at(i)->type()); } tree declaration_type = build_function_type_array(void_type_node, - definition->parameters().size(), parameter_types); + definition->parameters().size(), parameter_types.data()); this->main_fndecl = build_fn_decl(definition->identifier().c_str(), declaration_type); this->symbol_map->enter(definition->identifier(), source::make_info(this->main_fndecl)); @@ -166,6 +173,21 @@ namespace gcc DECL_RESULT(this->main_fndecl) = resdecl; enter_scope(); + + gcc::tree_chain argument_chain; + for (std::size_t i = 0; i < definition->parameters().size(); ++i) + { + auto parameter = definition->parameters().at(i); + + tree declaration_tree = build_decl(get_location(¶meter->position()), PARM_DECL, + get_identifier(parameter->identifier().c_str()), parameter_types[i]); + DECL_CONTEXT(declaration_tree) = this->main_fndecl; + DECL_ARG_TYPE(declaration_tree) = parameter_types[i]; + + this->symbol_map->enter(parameter->identifier(), source::make_info(declaration_tree)); + argument_chain.append(declaration_tree); + } + DECL_ARGUMENTS(this->main_fndecl) = argument_chain.head(); definition->body().accept(this); tree_symbol_mapping mapping = leave_scope(); @@ -180,8 +202,6 @@ namespace gcc gimplify_function_tree(this->main_fndecl); cgraph_node::finalize_function(this->main_fndecl, true); - - free(parameter_types); } void generic_visitor::enter_scope() diff --git a/include/elna/source/semantic.h b/include/elna/source/semantic.h new file mode 100644 index 0000000..0c1e223 --- /dev/null +++ b/include/elna/source/semantic.h @@ -0,0 +1,100 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#pragma once + +#include +#include "elna/source/ast.h" +#include "elna/source/symbol_table.h" + +namespace elna +{ +namespace source +{ + class name_analysis_visitor final : public empty_visitor + { + std::shared_ptr table; + const char *filename; + std::list> m_errors; + const std::size_t pointer_size; + + std::shared_ptr convert_declaration_type(const type_expression& ast_type) const; + + public: + /** + * \param table Symbol table. + * \param path Source filename. + * \param target_pointer_size Pointer size on the target platform. + */ + name_analysis_visitor(std::shared_ptr table, const char *filename, + const std::size_t target_pointer_size); + + /** + * \return Collected errors. + */ + const std::list>& errors() const noexcept; + + void visit(constant_definition *definition) override; + void visit(declaration *declaration) override; + void visit(program *program) override; + void visit(procedure_definition *procedure) override; + }; + + /** + * Visitor which allocates registers and stack space for variables and + * parameters. + */ + class allocator_visitor final : public empty_visitor + { + std::ptrdiff_t local_offset; + std::ptrdiff_t argument_offset; + std::shared_ptr table; + + public: + allocator_visitor(std::shared_ptr table); + + void visit(declaration *declaration) override; + void visit(program *program) override; + void visit(procedure_definition *procedure) override; + void visit(call_statement *statement) override; + }; + + /** + * This visitor performs the type checking. + */ + class type_analysis_visitor final : public empty_visitor + { + std::shared_ptr table; + const char *filename; + const std::size_t pointer_size; + std::list> m_errors; + + public: + /** + * \param table Symbol table. + * \param path Source filename. + * \param target_pointer_size Pointer size on the target platform. + */ + type_analysis_visitor(std::shared_ptr table, const char *filename, + const std::size_t target_pointer_size); + + /** + * \return Collected errors. + */ + const std::list>& errors() const noexcept; + + void visit(program *program) override; + void visit(procedure_definition *procedure) override; + void visit(integer_literal *literal) override; + void visit(boolean_literal *literal) override; + void visit(variable_expression *expression) override; + void visit(unary_expression *expression) override; + void visit(binary_expression *expression) override; + void visit(call_statement *statement) override; + void visit(constant_definition *definition) override; + void visit(while_statement *statement) override; + void visit(if_statement *statement) override; + void visit(assign_statement *statement) override; + }; +} +} diff --git a/include/elna/source/symbol_table.h b/include/elna/source/symbol_table.h new file mode 100644 index 0000000..b240781 --- /dev/null +++ b/include/elna/source/symbol_table.h @@ -0,0 +1,198 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#pragma once + +#include +#include +#include +#include + +namespace elna +{ +namespace source +{ + class symbol_table; + class type_info; + class typed_info; + class constant_info; + class variable_info; + class parameter_info; + class intrinsic_info; + class procedure_info; + + /** + * Generic language entity information. + */ + class info + { + public: + virtual ~info() = 0; + + virtual type_info *is_type_info() noexcept; + virtual typed_info *is_typed_info() noexcept; + virtual constant_info *is_constant_info() noexcept; + virtual variable_info *is_variable_info() noexcept; + virtual parameter_info *is_parameter_info() noexcept; + virtual intrinsic_info *is_intrinsic_info() noexcept; + virtual procedure_info *is_procedure_info() noexcept; + + protected: + info(); + }; + + /** + * Type information. + */ + class type_info final : public info + { + std::shared_ptr m_type; + + public: + explicit type_info(std::shared_ptr type); + ~type_info() override; + + virtual type_info *is_type_info() noexcept override; + std::shared_ptr type() const noexcept; + }; + + /** + * Information for a typed symbol. + */ + class typed_info : public info + { + std::shared_ptr m_type; + + protected: + typed_info(std::shared_ptr type); + + public: + ~typed_info() override; + + virtual typed_info *is_typed_info() noexcept override; + std::shared_ptr type() const noexcept; + }; + + /** + * Constant information. + */ + class constant_info final : public typed_info + { + std::int32_t m_value; + + public: + constant_info(std::shared_ptr type, const std::int32_t value); + + virtual constant_info *is_constant_info() noexcept override; + std::int32_t value() const noexcept; + }; + + /** + * Variable information. + */ + class variable_info final : public typed_info + { + public: + std::ptrdiff_t offset{ 0 }; + + explicit variable_info(std::shared_ptr type); + + virtual variable_info *is_variable_info() noexcept override; + }; + + /** + * Procedure parameter information. + */ + class parameter_info final : public typed_info + { + public: + std::ptrdiff_t offset{ 0 }; + + explicit parameter_info(std::shared_ptr type); + + virtual parameter_info *is_parameter_info() noexcept override; + }; + + /** + * Intrinsic and external procedure information. + */ + class intrinsic_info : public info + { + std::shared_ptr m_type; + + public: + explicit intrinsic_info(const class procedure_type& type); + ~intrinsic_info() override; + + std::shared_ptr type() const noexcept; + std::size_t parameter_stack_size() const noexcept; + virtual intrinsic_info *is_intrinsic_info() noexcept override; + }; + + /** + * Procedure information. + */ + class procedure_info final : public intrinsic_info + { + std::shared_ptr local_table; + + public: + std::size_t local_stack_size{ 0 }; + std::size_t argument_stack_size{ 0 }; + + procedure_info(const class procedure_type& type, std::shared_ptr outer_scope); + ~procedure_info() override; + + std::shared_ptr scope(); + std::size_t stack_size() const noexcept; + virtual procedure_info *is_procedure_info() noexcept override; + }; + + /** + * Symbol table. + */ + class symbol_table + { + std::unordered_map> entries; + std::shared_ptr outer_scope; + + public: + /** + * Constructs a new symbol with an optional outer scope. + * + * \param scope Outer scope. + */ + explicit symbol_table(std::shared_ptr scope = nullptr); + + /** + * Looks for symbol in the table by name. Returns nullptr if the symbol + * can not be found. + * + * \param name Symbol name. + * \return Symbol from the table if found. + */ + std::shared_ptr lookup(const std::string& name); + + /** + * Registers new symbol. + * + * \param name Symbol name. + * \param entry Symbol information. + */ + void enter(const std::string& name, std::shared_ptr entry); + + /** + * Returns the outer scope or nullptr if the this is the global scope. + * + * \return Outer scope. + */ + std::shared_ptr scope(); + }; + + /** + * Creates a symbol table with predefined symbols. + * + * \return A symbol table with predefined symbols. + */ + std::shared_ptr add_builtin_symbols();} +} diff --git a/source/semantic.cc b/source/semantic.cc new file mode 100644 index 0000000..1086ba4 --- /dev/null +++ b/source/semantic.cc @@ -0,0 +1,325 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "elna/source/semantic.h" +#include "elna/source/result.h" +#include + +namespace elna +{ +namespace source +{ + name_analysis_visitor::name_analysis_visitor(std::shared_ptr table, + const char *filename, const std::size_t target_pointer_size) + : table(table), filename(filename), pointer_size(target_pointer_size) + { + } + + void name_analysis_visitor::visit(constant_definition *definition) + { + auto constant_type = std::make_shared(int_type); + this->table->enter(definition->identifier(), + std::make_shared(constant_type, definition->body().number())); + } + + std::shared_ptr name_analysis_visitor::convert_declaration_type(const type_expression& ast_type) const + { + auto variable_type = table->lookup(ast_type.base())->is_type_info()->type(); + std::shared_ptr declaration_type; + + if (ast_type.is_pointer()) + { + return std::make_shared(variable_type, 4); + } + else + { + return variable_type; + } + } + + void name_analysis_visitor::visit(declaration *declarationx) + { + std::shared_ptr declaration_type = convert_declaration_type(declarationx->type()); + + this->table->enter(declarationx->identifier(), + std::make_shared(declaration_type)); + } + + void name_analysis_visitor::visit(program *program) + { + class procedure_type main_type{ std::vector>(), this->pointer_size }; + this->table->enter("_start", std::make_shared(main_type, this->table)); + empty_visitor::visit(program); + } + + void name_analysis_visitor::visit(procedure_definition *procedure) + { + std::vector> arguments; + + for (auto& parameter : procedure->parameters()) + { + auto declaration_type = convert_declaration_type(parameter->type()); + arguments.push_back(declaration_type); + } + procedure_type definition_type{ std::move(arguments), this->pointer_size }; + auto info = std::make_shared(definition_type, this->table); + + this->table->enter(procedure->identifier(), info); + this->table = info->scope(); + + for (std::size_t i = 0; i < procedure->parameters().size(); ++i) + { + this->table->enter(procedure->parameters().at(i)->identifier(), + std::make_shared(definition_type.arguments.at(i))); + } + procedure->body().accept(this); + + this->table = info->scope()->scope(); + } + + const std::list>& name_analysis_visitor::errors() const noexcept + { + return m_errors; + } + + allocator_visitor::allocator_visitor(std::shared_ptr table) + : table(table) + { + } + + void allocator_visitor::visit(declaration *declaration) + { + auto declaration_info = this->table->lookup(declaration->identifier()); + + if (auto variable = declaration_info->is_variable_info()) + { + this->local_offset -= sizeof(std::int32_t); + variable->offset = this->local_offset; + } + else if (auto parameter = declaration_info->is_parameter_info()) + { + parameter->offset = this->argument_offset; + this->argument_offset += sizeof(std::int32_t); + } + } + + void allocator_visitor::visit(program *program) + { + this->local_offset = 0; + this->argument_offset = 0; + + empty_visitor::visit(program); + table->lookup("_start")->is_procedure_info()->local_stack_size = + std::abs(this->local_offset); + } + + void allocator_visitor::visit(procedure_definition *procedure) + { + this->local_offset = 0; + this->argument_offset = 0; + auto info = this->table->lookup(procedure->identifier())->is_procedure_info(); + this->table = info->scope(); + + empty_visitor::visit(procedure); + + this->table = info->scope()->scope(); + info->local_stack_size = std::abs(this->local_offset); + info->argument_stack_size = this->argument_offset; + } + + void allocator_visitor::visit(call_statement *statement) + { + auto call_info = this->table->lookup(statement->name())->is_intrinsic_info(); + + this->argument_offset = std::max(static_cast(this->argument_offset), + call_info->parameter_stack_size()); + } + + type_analysis_visitor::type_analysis_visitor(std::shared_ptr table, + const char *filename, const std::size_t target_pointer_size) + : table(table), filename(filename), pointer_size(target_pointer_size) + { + } + + void type_analysis_visitor::visit(program *program) + { + for (auto& definition : program->definitions()) + { + definition->accept(this); + } + program->body().accept(this); + } + + void type_analysis_visitor::visit(procedure_definition *procedure) + { + auto info = this->table->lookup(procedure->identifier())->is_procedure_info(); + this->table = info->scope(); + + procedure->body().accept(this); + + this->table = info->scope()->scope(); + } + + void type_analysis_visitor::visit(integer_literal *literal) + { + literal->data_type = table->lookup("Int")->is_type_info()->type(); + } + + void type_analysis_visitor::visit(boolean_literal *literal) + { + literal->data_type = table->lookup("Boolean")->is_type_info()->type(); + } + + void type_analysis_visitor::visit(variable_expression *expression) + { + expression->data_type = table->lookup(expression->name())->is_typed_info()->type(); + } + + void type_analysis_visitor::visit(unary_expression *expression) + { + empty_visitor::visit(expression); + + switch (expression->operation()) + { + case unary_operator::reference: + expression->data_type = std::make_shared(expression->operand().data_type, + this->pointer_size); + break; + case unary_operator::dereference: + auto operand_type = expression->operand().data_type; + + if (operand_type->is_pointer_type() != nullptr) + { + expression->data_type = operand_type; + } + else if (operand_type != nullptr) + { + auto new_error = std::make_unique(operand_type, + type_mismatch::operation::dereference, this->filename, expression->position()); + m_errors.push_back(std::move(new_error)); + } + break; + } + } + + void type_analysis_visitor::visit(binary_expression *expression) + { + empty_visitor::visit(expression); + + switch (expression->operation()) + { + case binary_operator::sum: + case binary_operator::subtraction: + case binary_operator::multiplication: + case binary_operator::division: + case binary_operator::less: + case binary_operator::greater: + case binary_operator::less_equal: + case binary_operator::greater_equal: + if (expression->lhs().data_type != nullptr && expression->rhs().data_type != nullptr) + { + std::unique_ptr new_error; + + if (*expression->lhs().data_type != int_type) + { + new_error = std::make_unique(expression->lhs().data_type, + type_mismatch::operation::arithmetic, this->filename, expression->lhs().position()); + } + if (*expression->rhs().data_type != int_type) + { + new_error = std::make_unique(expression->rhs().data_type, + type_mismatch::operation::arithmetic, this->filename, expression->rhs().position()); + } + if (new_error != nullptr) + { + m_errors.push_back(std::move(new_error)); + } + } + break; + case binary_operator::equals: + case binary_operator::not_equals: + if (expression->lhs().data_type != nullptr && expression->rhs().data_type != nullptr) + { + if (expression->lhs().data_type != expression->rhs().data_type) + { + auto new_error = std::make_unique(expression->rhs().data_type, + type_mismatch::operation::comparison, this->filename, expression->rhs().position()); + } + } + break; + } + } + + void type_analysis_visitor::visit(call_statement *statement) + { + auto call_info = this->table->lookup(statement->name())->is_intrinsic_info(); + + std::size_t i{ 0 }; + for (const auto& argument : statement->arguments()) + { + argument->accept(this); + + if (argument->data_type != nullptr && i < call_info->type()->arguments.size() + && call_info->type()->arguments[i] != argument->data_type) + { + auto new_error = std::make_unique(argument->data_type, + type_mismatch::operation::argument, this->filename, argument->position()); + m_errors.push_back(std::move(new_error)); + } + + ++i; + } + } + + void type_analysis_visitor::visit(constant_definition *definition) + { + definition->body().accept(this); + } + + void type_analysis_visitor::visit(while_statement *statement) + { + statement->prerequisite().accept(this); + auto condition_type = statement->prerequisite().data_type; + + if (condition_type != nullptr && *condition_type != boolean_type) + { + auto new_error = std::make_unique(condition_type, + type_mismatch::operation::condition, this->filename, statement->prerequisite().position()); + m_errors.push_back(std::move(new_error)); + } + statement->body().accept(this); + } + + void type_analysis_visitor::visit(if_statement *statement) + { + statement->prerequisite().accept(this); + auto condition_type = statement->prerequisite().data_type; + + if (condition_type != nullptr && *condition_type != boolean_type) + { + auto new_error = std::make_unique(condition_type, + type_mismatch::operation::condition, this->filename, statement->prerequisite().position()); + m_errors.push_back(std::move(new_error)); + } + statement->body().accept(this); + } + + void type_analysis_visitor::visit(assign_statement *statement) + { + statement->rvalue().accept(this); + auto lvalue_info = this->table->lookup(statement->lvalue())->is_typed_info(); + + if (statement->rvalue().data_type != nullptr && lvalue_info->type() == statement->rvalue().data_type) + { + auto new_error = std::make_unique(statement->rvalue().data_type, + type_mismatch::operation::assignment, this->filename, statement->position()); + m_errors.push_back(std::move(new_error)); + } + } + + const std::list>& type_analysis_visitor::errors() const noexcept + { + return m_errors; + } +} +} diff --git a/source/symbol_table.cc b/source/symbol_table.cc new file mode 100644 index 0000000..327781d --- /dev/null +++ b/source/symbol_table.cc @@ -0,0 +1,233 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "elna/source/types.h" +#include "elna/source/symbol_table.h" + +namespace elna +{ +namespace source +{ + symbol_table::symbol_table(std::shared_ptr scope) + : outer_scope(scope) + { + } + + std::shared_ptr symbol_table::lookup(const std::string& name) + { + auto entry = entries.find(name); + + if (entry != entries.cend()) + { + return entry->second; + } + if (this->outer_scope != nullptr) + { + return this->outer_scope->lookup(name); + } + return nullptr; + } + + void symbol_table::enter(const std::string& name, std::shared_ptr entry) + { + entries.insert({ name, entry }); + } + + std::shared_ptr symbol_table::scope() + { + return this->outer_scope; + } + + info::~info() + { + } + + type_info *info::is_type_info() noexcept + { + return nullptr; + } + + typed_info *info::is_typed_info() noexcept + { + return nullptr; + } + + constant_info *info::is_constant_info() noexcept + { + return nullptr; + } + + variable_info *info::is_variable_info() noexcept + { + return nullptr; + } + + parameter_info *info::is_parameter_info() noexcept + { + return nullptr; + } + + intrinsic_info *info::is_intrinsic_info() noexcept + { + return nullptr; + } + + procedure_info *info::is_procedure_info() noexcept + { + return nullptr; + } + + info::info() + { + } + + type_info::type_info(std::shared_ptr type) + : info(), m_type(type) + { + } + + type_info::~type_info() + { + } + + std::shared_ptr type_info::type() const noexcept + { + return m_type; + } + + type_info *type_info::is_type_info() noexcept + { + return this; + } + + typed_info::typed_info(std::shared_ptr type) + : m_type(type) + { + } + + typed_info::~typed_info() + { + } + + typed_info *typed_info::is_typed_info() noexcept + { + return this; + } + + std::shared_ptr typed_info::type() const noexcept + { + return m_type; + } + + constant_info::constant_info(const std::shared_ptr type, const std::int32_t value) + : typed_info(type), m_value(value) + { + } + + constant_info *constant_info::is_constant_info() noexcept + { + return this; + } + + std::int32_t constant_info::value() const noexcept + { + return m_value; + } + + variable_info::variable_info(std::shared_ptr type) + : typed_info(type) + { + } + + variable_info *variable_info::is_variable_info() noexcept + { + return this; + } + + parameter_info::parameter_info(std::shared_ptr type) + : typed_info(type) + { + } + + parameter_info *parameter_info::is_parameter_info() noexcept + { + return this; + } + + intrinsic_info::intrinsic_info(const class procedure_type& type) + : m_type(std::make_shared(type)) + { + } + + intrinsic_info::~intrinsic_info() + { + } + + std::shared_ptr intrinsic_info::type() const noexcept + { + return m_type; + } + + std::size_t intrinsic_info::parameter_stack_size() const noexcept + { + return type()->arguments.size() * sizeof(std::int32_t); + } + + intrinsic_info *intrinsic_info::is_intrinsic_info() noexcept + { + return this; + } + + procedure_info::procedure_info(const class procedure_type& type, std::shared_ptr outer_scope) + : intrinsic_info(type), local_table(std::make_shared(outer_scope)) + { + } + + procedure_info::~procedure_info() + { + } + + std::shared_ptr procedure_info::scope() + { + return local_table; + } + + std::size_t procedure_info::stack_size() const noexcept + { + return local_stack_size + argument_stack_size; + } + + procedure_info *procedure_info::is_procedure_info() noexcept + { + return this; + } + + constexpr std::size_t pointer_size = 4; + + std::shared_ptr add_builtin_symbols() + { + source::symbol_table result; + std::vector> intrinsic_arguments; + + auto prim = boolean_type; + auto boolean_info = std::make_shared(std::make_shared(boolean_type)); + auto int_info = std::make_shared(std::make_shared(int_type)); + result.enter("Boolean", boolean_info); + result.enter("Int", int_info); + + intrinsic_arguments.push_back(int_info->type()); + auto writei = std::make_shared( + source::procedure_type{ intrinsic_arguments, pointer_size }); + result.enter("writei", writei); + intrinsic_arguments.clear(); + + intrinsic_arguments.push_back(boolean_info->type()); + auto writeb = std::make_shared( + source::procedure_type{ intrinsic_arguments, pointer_size }); + result.enter("writeb", writeb); + intrinsic_arguments.clear(); + + return std::make_shared(std::move(result)); + } +} +}