From b8bde3e69ff07c381ddf879a2d835cf416e3a1be Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Thu, 14 Mar 2024 16:50:47 +0100 Subject: [PATCH 01/10] Draft: Support for record extension. --- src/analyzer/LambdaLifter.cpp | 2 +- src/codegen/llvm/LLVMIRBuilder.cpp | 9 +++- src/data/ast/ASTContext.cpp | 10 +---- src/data/ast/ASTContext.h | 11 ++--- src/data/ast/RecordTypeNode.cpp | 11 ++++- src/data/ast/RecordTypeNode.h | 7 ++-- src/data/symtab/SymbolImporter.cpp | 3 +- src/parser/Parser.cpp | 21 +++++++--- src/sema/Sema.cpp | 67 ++++++++++++++++++++++-------- src/sema/Sema.h | 3 +- test/c/Arrays.c | 63 ++++++++++++---------------- test/oberon/RecordExt.Mod | 42 +++++++++++++++++++ 12 files changed, 165 insertions(+), 84 deletions(-) create mode 100644 test/oberon/RecordExt.Mod diff --git a/src/analyzer/LambdaLifter.cpp b/src/analyzer/LambdaLifter.cpp index 7c625ec..e7e1330 100644 --- a/src/analyzer/LambdaLifter.cpp +++ b/src/analyzer/LambdaLifter.cpp @@ -52,7 +52,7 @@ void LambdaLifter::visit(ProcedureNode &node) { auto ident = make_unique(var->getIdentifier()->name()); fields.push_back(make_unique(EMPTY_POS, std::move(ident), var->getType())); } - auto type = context_->getOrInsertRecordType(EMPTY_POS, EMPTY_POS, identifier.get(), std::move(fields)); + auto type = context_->getOrInsertRecordType(EMPTY_POS, EMPTY_POS, identifier.get(), nullptr, std::move(fields)); auto decl = make_unique(EMPTY_POS, std::move(identifier), type); decl->setLevel(module_->getLevel() + 1); module_->addTypeDeclaration(std::move(decl)); diff --git a/src/codegen/llvm/LLVMIRBuilder.cpp b/src/codegen/llvm/LLVMIRBuilder.cpp index 3923c37..b4aa92c 100644 --- a/src/codegen/llvm/LLVMIRBuilder.cpp +++ b/src/codegen/llvm/LLVMIRBuilder.cpp @@ -301,13 +301,18 @@ TypeNode *LLVMIRBuilder::selectors(TypeNode *base, SelectorIterator start, Selec // handle record field access auto field = dynamic_cast(sel)->getField(); auto record_t = dynamic_cast(selector_t); + auto recordTy = getLLVMType(record_t); + value = processGEP(base, value, indices); + // get address of first field + auto layout = module_->getDataLayout().getStructLayout((StructType *)recordTy); + auto offset = layout->getElementOffset(0); for (size_t pos = 0; pos < record_t->getFieldCount(); pos++) { if (field == record_t->getField(pos)) { - indices.push_back(builder_.getInt32(pos)); + offset = layout->getElementOffset(pos) - offset; + value = builder_.CreateInBoundsGEP(builder_.getInt8Ty(), value, {builder_.getInt64(offset)}); break; } } - value = processGEP(base, value, indices); selector_t = field->getType(); base = selector_t; } else if (sel->getNodeType() == NodeType::type) { diff --git a/src/data/ast/ASTContext.cpp b/src/data/ast/ASTContext.cpp index 821f462..293a783 100644 --- a/src/data/ast/ASTContext.cpp +++ b/src/data/ast/ASTContext.cpp @@ -23,12 +23,6 @@ ASTContext::setTranslationUnit(unique_ptr module) { module_ = std::move(module); } -ArrayTypeNode * -ASTContext::getOrInsertArrayType(const FilePos &start, const FilePos &end, - Ident *ident, unsigned length, TypeNode *memberType) { - return getOrInsertArrayType(start, end, ident, 1, { length }, { memberType }); -} - ArrayTypeNode * ASTContext::getOrInsertArrayType(const FilePos &start, [[maybe_unused]] const FilePos &end, Ident *ident, unsigned dimensions, vector lengths, vector types) { @@ -60,8 +54,8 @@ ASTContext::getOrInsertArrayType(const FilePos &start, [[maybe_unused]] const Fi RecordTypeNode * ASTContext::getOrInsertRecordType(const FilePos &start, [[maybe_unused]] const FilePos &end, - Ident *ident, vector> fields) { - auto type = make_unique(start, ident, std::move(fields)); + Ident *ident, RecordTypeNode *base, vector> fields) { + auto type = make_unique(start, ident, base, std::move(fields)); auto res = type.get(); record_ts_.push_back(std::move(type)); return res; diff --git a/src/data/ast/ASTContext.h b/src/data/ast/ASTContext.h index da13aab..9332d0b 100644 --- a/src/data/ast/ASTContext.h +++ b/src/data/ast/ASTContext.h @@ -47,11 +47,12 @@ class ASTContext { [[nodiscard]] ModuleNode *getTranslationUnit() const; void setTranslationUnit(unique_ptr); - [[deprecated]] - ArrayTypeNode *getOrInsertArrayType(const FilePos &, const FilePos &, Ident *, unsigned, TypeNode *); - ArrayTypeNode *getOrInsertArrayType(const FilePos &, const FilePos &, Ident *, unsigned, vector, vector); - RecordTypeNode *getOrInsertRecordType(const FilePos &, const FilePos &, Ident *, vector>); - PointerTypeNode *getOrInsertPointerType(const FilePos &, const FilePos &, Ident *, TypeNode *); + ArrayTypeNode *getOrInsertArrayType(const FilePos &, const FilePos &, + Ident *, unsigned, vector, vector); + RecordTypeNode *getOrInsertRecordType(const FilePos &, const FilePos &, + Ident *, RecordTypeNode *, vector>); + PointerTypeNode *getOrInsertPointerType(const FilePos &, const FilePos &, + Ident *, TypeNode *); ProcedureTypeNode *getOrInsertProcedureType(const FilePos &, const FilePos &, Ident *, vector>, bool, TypeNode *); diff --git a/src/data/ast/RecordTypeNode.cpp b/src/data/ast/RecordTypeNode.cpp index e23b68a..a89ff25 100644 --- a/src/data/ast/RecordTypeNode.cpp +++ b/src/data/ast/RecordTypeNode.cpp @@ -32,8 +32,15 @@ size_t RecordTypeNode::getFieldCount() { return fields_.size(); } -void RecordTypeNode::setBaseType(RecordTypeNode *base) { - base_ = base; +bool RecordTypeNode::isExtened() const { + return base_ != nullptr; +} + +bool RecordTypeNode::instanceOf(RecordTypeNode *type) const { + if (this != type && base_) { + return base_->instanceOf(type); + } + return this == type; } RecordTypeNode *RecordTypeNode::getBaseType() const { diff --git a/src/data/ast/RecordTypeNode.h b/src/data/ast/RecordTypeNode.h index f726534..6f9e7e1 100644 --- a/src/data/ast/RecordTypeNode.h +++ b/src/data/ast/RecordTypeNode.h @@ -28,9 +28,9 @@ class RecordTypeNode final : public TypeNode { RecordTypeNode *base_; public: - RecordTypeNode(const FilePos &pos, Ident *ident, vector> fields) : + RecordTypeNode(const FilePos &pos, Ident *ident, RecordTypeNode *base, vector> fields) : TypeNode(NodeType::record_type, pos, ident, TypeKind::RECORD, 0), - fields_(std::move(fields)), base_() {}; + fields_(std::move(fields)), base_(base) {}; ~RecordTypeNode() final = default; [[nodiscard]] unsigned int getSize() const final; @@ -39,7 +39,8 @@ class RecordTypeNode final : public TypeNode { [[nodiscard]] FieldNode *getField(size_t num) const; [[nodiscard]] size_t getFieldCount(); - void setBaseType(RecordTypeNode *base); + [[nodiscard]] bool isExtened() const; + [[nodiscard]] bool instanceOf(RecordTypeNode *) const; [[nodiscard]] RecordTypeNode *getBaseType() const; void accept(NodeVisitor &visitor) final; diff --git a/src/data/symtab/SymbolImporter.cpp b/src/data/symtab/SymbolImporter.cpp index bf19e63..480895b 100644 --- a/src/data/symtab/SymbolImporter.cpp +++ b/src/data/symtab/SymbolImporter.cpp @@ -217,8 +217,7 @@ TypeNode *SymbolImporter::readRecordType(SymbolFile *file) { // check for terminator ch = file->readChar(); } - auto res = context_->getOrInsertRecordType(EMPTY_POS, EMPTY_POS, nullptr, std::move(fields)); - res->setBaseType(base_t); + auto res = context_->getOrInsertRecordType(EMPTY_POS, EMPTY_POS, nullptr, base_t, std::move(fields)); res->setSize(size); return res; } \ No newline at end of file diff --git a/src/parser/Parser.cpp b/src/parser/Parser.cpp index 1ab9ee6..06447bd 100644 --- a/src/parser/Parser.cpp +++ b/src/parser/Parser.cpp @@ -295,23 +295,32 @@ ArrayTypeNode* Parser::array_type(Ident* identifier) { return nullptr; } -// TODO record_type = "RECORD" [ "(" qualident ")" ] [ field_list { ";" field_list } ] END. -// record_type = "RECORD" field_list { ";" field_list } "END" . +// record_type = "RECORD" [ "(" qualident ")" ] [ field_list { ";" field_list } ] END. RecordTypeNode* Parser::record_type(Ident* identifier) { logger_.debug("record_type"); FilePos pos = scanner_.next()->start(); // skip RECORD keyword and get its position + unique_ptr base; + if (scanner_.peek()->type() == TokenType::lparen) { + scanner_.next(); // skip left parenthesis + base = qualident(); + if (assertToken(scanner_.peek(), TokenType::rparen)) { + scanner_.next(); // skip right parenthesis + } + } vector> fields; - field_list(fields); - while (scanner_.peek()->type() == TokenType::semicolon) { - scanner_.next(); + if (scanner_.peek()->type() == TokenType::const_ident) { field_list(fields); + while (scanner_.peek()->type() == TokenType::semicolon) { + scanner_.next(); // skip semicolon + field_list(fields); + } } if (assertToken(scanner_.peek(), TokenType::kw_end)) { scanner_.next(); } // [<)>, <;>, ] // resync({ TokenType::semicolon, TokenType::rparen, TokenType::kw_end }); - return sema_.onRecordType(pos, EMPTY_POS, identifier, std::move(fields)); + return sema_.onRecordType(pos, EMPTY_POS, identifier, std::move(base), std::move(fields)); } // field_list = ident_list ":" type . diff --git a/src/sema/Sema.cpp b/src/sema/Sema.cpp index 0084c4c..fdd7125 100644 --- a/src/sema/Sema.cpp +++ b/src/sema/Sema.cpp @@ -231,7 +231,7 @@ Sema::onPointerType([[maybe_unused]] const FilePos &start, const FilePos &end, } ProcedureTypeNode * -Sema::onProcedureType([[maybe_unused]] const FilePos &start, const FilePos &end, +Sema::onProcedureType(const FilePos &start, const FilePos &end, Ident *ident, vector> params, bool varargs, TypeNode *ret) { return context_->getOrInsertProcedureType(start, end, ident, std::move(params), varargs, ret); } @@ -251,11 +251,25 @@ Sema::onParameter(const FilePos &start, [[maybe_unused]] const FilePos &end, RecordTypeNode * Sema::onRecordType(const FilePos &start, const FilePos &end, - Ident *ident, vector> fields) { - if (fields.empty()) { - logger_.error(start, "records needs at least one field."); + Ident *name, unique_ptr ident, vector> fields) { + RecordTypeNode *base = nullptr; + if (ident) { + auto sym = symbols_->lookup(ident.get()); + if (sym && sym->getNodeType() == NodeType::type) { + auto type = dynamic_cast(sym)->getType(); + if (type->isRecord()) { + base = dynamic_cast(type); + } else { + logger_.error(ident->start(), "base type must be a record type."); + } + } else { + logger_.error(start, "undefined type: " + to_string(*ident) + "."); + } } - auto node = context_->getOrInsertRecordType(start, end, ident, std::move(fields)); +// if (fields.empty()) { +// logger_.error(start, "records needs at least one field."); +// } + auto node = context_->getOrInsertRecordType(start, end, name, base, std::move(fields)); set names; for (size_t i = 0; i < node->getFieldCount(); i++) { auto field = node->getField(i); @@ -698,16 +712,18 @@ Sema::assertAssignable(const ExpressionNode *expr, string &err) const { return false; } else if (expr->getNodeType() == NodeType::qualified_expression) { auto decl = dynamic_cast(expr)->dereference(); - if (decl->getNodeType() == NodeType::parameter) { - auto type = decl->getType(); - if (type->isStructured()) { - auto param = dynamic_cast(decl); - err = "a non-variable structured parameter"; - return param->isVar(); + if (decl) { + if (decl->getNodeType() == NodeType::parameter) { + auto type = decl->getType(); + if (type->isStructured()) { + auto param = dynamic_cast(decl); + err = "a non-variable structured parameter"; + return param->isVar(); + } + } else if (decl->getNodeType() == NodeType::constant) { + err = "a constant"; + return false; } - } else if (decl->getNodeType() == NodeType::constant) { - err = "a constant"; - return false; } err = ""; return true; @@ -842,14 +858,29 @@ FieldNode *Sema::onRecordField(TypeNode *base, RecordField *sel) { } TypeNode *Sema::onTypeguard(DeclarationNode *sym, [[maybe_unused]] TypeNode *base, Typeguard *sel) { - auto type = symbols_->lookup(sel->ident()); - if (type) { + auto decl = symbols_->lookup(sel->ident()); + if (decl) { // O07.8.1: in v(T), v is a variable parameter of record type, or v is a pointer. if ((sym->getNodeType() == NodeType::parameter && dynamic_cast(sym)->isVar() && sym->getType()->isRecord()) || sym->getType()->isPointer()) { - if (type->getNodeType() == NodeType::type) { + if (decl->getNodeType() == NodeType::type) { + auto type = dynamic_cast(decl)->getType(); // TODO check if type-guard is compatible with base type. - return dynamic_cast(type)->getType(); + if (type->isPointer()) { + type = dynamic_cast(type)->getBase(); + } + if (type->isRecord()) { + auto actual = dynamic_cast(sym->getType()); + auto guard = dynamic_cast(type); + if (guard->instanceOf(actual)) { + return guard; + } else { + logger_.error(sel->pos(), "type mismatch: " + format(guard) + " is not an extension of " + + format(actual) + "."); + } + } else { + logger_.error(sel->pos(), "record type or pointer to record type expected."); + } } else { logger_.error(sel->pos(), "unexpected selector."); } diff --git a/src/sema/Sema.h b/src/sema/Sema.h index e891fbc..24b0a99 100644 --- a/src/sema/Sema.h +++ b/src/sema/Sema.h @@ -117,7 +117,8 @@ class Sema { unique_ptr onParameter(const FilePos &, const FilePos &, unique_ptr, TypeNode *, bool, unsigned = 0); - RecordTypeNode *onRecordType(const FilePos &, const FilePos &, Ident *, vector>); + RecordTypeNode *onRecordType(const FilePos &, const FilePos &, + Ident *, unique_ptr, vector>); unique_ptr onField(const FilePos&, const FilePos&, unique_ptr, TypeNode*, unsigned = 0); TypeNode *onTypeReference(const FilePos &, const FilePos &, unique_ptr, unsigned = 0); diff --git a/test/c/Arrays.c b/test/c/Arrays.c index defeb47..54c6507 100644 --- a/test/c/Arrays.c +++ b/test/c/Arrays.c @@ -4,6 +4,7 @@ #include #include +#include #include const int SIZE = 5000; @@ -11,48 +12,38 @@ const int MAXVAL = 10 * SIZE; int a[5000]; -struct Array { +typedef struct { int dim; - int vec[]; -}; + float vec[10]; + int test; +} Array; -void print(int value[]) { - for (int i = 0; i < SIZE; i++) { - printf("%d ", value[i]); - } -} - -void destroy(int value[]) { - for (int i = 0; i < SIZE; i++) { - value[i] = i; - } -} - -void Len(struct Array *arr) { - arr->dim = 100; - arr->vec[99] = 20; -} +//void print(int value[]) { +// for (int i = 0; i < SIZE; i++) { +// printf("%d ", value[i]); +// } +//} -struct Array getArray() { - struct Array s; - return s; -} +//void destroy(int value[]) { +// for (int i = 0; i < SIZE; i++) { +// value[i] = i; +// } +//} -void setArray(struct Array s) { - s.dim = 100; - s.vec[99] = 20; +void test(Array *rec) { + int test = *((int*)(((char*)rec) + (offsetof(Array, test) - offsetof(Array, dim)))); + printf("%d\n", test); } int main(int argc, const char* argv[]) { -// int a[SIZE]; -// srand(time(NULL)); -// for (int i = 0; i < SIZE; i++) { -// a[i] = rand() % 100 + 1; -// } -// print(a); -// destroy(a); -// print(a); - struct Array s; - setArray(s); + Array m[10]; + Array s; + s.dim = 10; + s.test = 20; + m[5] = s; + + int len = *((int*)(((char*)m[5].vec) + (offsetof(Array, dim) - offsetof(Array, vec)))); + printf("%d\n", len); + test(&s); return 0; } diff --git a/test/oberon/RecordExt.Mod b/test/oberon/RecordExt.Mod new file mode 100644 index 0000000..96a59ad --- /dev/null +++ b/test/oberon/RecordExt.Mod @@ -0,0 +1,42 @@ +MODULE RecordExt; +IMPORT Out; + +TYPE + ObjectDesc = RECORD END; + Node = POINTER TO NodeDesc; + NodeDesc = RECORD (ObjectDesc) + int: INTEGER; + next: Node + END; + Point2D = RECORD (ObjectDesc) + x, y: INTEGER + END; + Point3D = RECORD (Point2D) + z: INTEGER + END; + Shape = RECORD (ObjectDesc) + area: REAL + END; + Circle = RECORD (Shape) + centre: Point2D; + radius: REAL + END; + Sphere = RECORD (ObjectDesc) + centre: Point3D; + radius: REAL + END; + +VAR nd: NodeDesc; + circ: Circle; + +PROCEDURE Init(VAR c: Shape; r: REAL); +BEGIN + c(Circle).radius := r; + c(Sphere).radius := r +END Init; + +BEGIN + nd.int := 0; + Init(circ, 3.141); + Out.Real(circ.radius, 0); Out.Ln +END RecordExt. \ No newline at end of file From e35f9e0985cf1ec97854eb5b71a40dd8df8bb5ea Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Fri, 15 Mar 2024 07:03:22 +0100 Subject: [PATCH 02/10] Draft: Support for record extension. - changes to the Parser to support declaration of extended records - changes in the AST to represent extended records - changes to the type checker in Sema to support subtype polymorphism for records --- src/sema/Sema.cpp | 20 +++++++++++--------- test/oberon/RecordExt.Mod | 14 +++++++++++--- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/sema/Sema.cpp b/src/sema/Sema.cpp index fdd7125..f947926 100644 --- a/src/sema/Sema.cpp +++ b/src/sema/Sema.cpp @@ -690,7 +690,7 @@ Sema::onSelectors(const FilePos &start, const FilePos &end, break; case NodeType::record_type: context = onRecordField(base, dynamic_cast(sel)); - base = context->getType(); + base = context ? context->getType() : nullTy_; break; case NodeType::type: base = onTypeguard(context, base, dynamic_cast(sel)); @@ -858,39 +858,41 @@ FieldNode *Sema::onRecordField(TypeNode *base, RecordField *sel) { } TypeNode *Sema::onTypeguard(DeclarationNode *sym, [[maybe_unused]] TypeNode *base, Typeguard *sel) { + FilePos start = sel->ident()->start(); auto decl = symbols_->lookup(sel->ident()); if (decl) { // O07.8.1: in v(T), v is a variable parameter of record type, or v is a pointer. if ((sym->getNodeType() == NodeType::parameter && dynamic_cast(sym)->isVar() && sym->getType()->isRecord()) || sym->getType()->isPointer()) { if (decl->getNodeType() == NodeType::type) { + RecordTypeNode *actual; auto type = dynamic_cast(decl)->getType(); - // TODO check if type-guard is compatible with base type. if (type->isPointer()) { type = dynamic_cast(type)->getBase(); } if (type->isRecord()) { - auto actual = dynamic_cast(sym->getType()); + actual = dynamic_cast(sym->getType()); auto guard = dynamic_cast(type); if (guard->instanceOf(actual)) { return guard; } else { - logger_.error(sel->pos(), "type mismatch: " + format(guard) + " is not an extension of " + logger_.error(start, "type mismatch: " + format(guard) + " is not an extension of " + format(actual) + "."); } } else { - logger_.error(sel->pos(), "record type or pointer to record type expected."); + logger_.error(start, "record type or pointer to record type expected."); } + return type; } else { - logger_.error(sel->pos(), "unexpected selector."); + logger_.error(start, "unexpected selector."); } } else { - logger_.error(sel->pos(), "a type guard can only be applied to a variable parameter of record type or a pointer."); + logger_.error(start, "a type guard can only be applied to a variable parameter of record type or a pointer."); } } else { - logger_.error(sel->pos(), "undefined identifier: " + to_string(*sel->ident()) + "."); + logger_.error(start, "undefined identifier: " + to_string(*sel->ident()) + "."); } - return nullptr; + return nullTy_; } unique_ptr diff --git a/test/oberon/RecordExt.Mod b/test/oberon/RecordExt.Mod index 96a59ad..928315f 100644 --- a/test/oberon/RecordExt.Mod +++ b/test/oberon/RecordExt.Mod @@ -5,9 +5,12 @@ TYPE ObjectDesc = RECORD END; Node = POINTER TO NodeDesc; NodeDesc = RECORD (ObjectDesc) - int: INTEGER; next: Node END; + IntNode = POINTER TO IntNodeDesc; + IntNodeDesc = RECORD (NodeDesc) + val: INTEGER + END; Point2D = RECORD (ObjectDesc) x, y: INTEGER END; @@ -26,8 +29,10 @@ TYPE radius: REAL END; -VAR nd: NodeDesc; +VAR nd: IntNodeDesc; circ: Circle; + node: Node; + inode: IntNode; PROCEDURE Init(VAR c: Shape; r: REAL); BEGIN @@ -38,5 +43,8 @@ END Init; BEGIN nd.int := 0; Init(circ, 3.141); - Out.Real(circ.radius, 0); Out.Ln + Out.Real(circ.radius, 0); Out.Ln; + NEW(inode); + node := inode; + node(IntNode).val := 42 END RecordExt. \ No newline at end of file From 67d6f1d531585f929da3365cd12ddb87403d25a4 Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Fri, 15 Mar 2024 13:38:51 +0100 Subject: [PATCH 03/10] Draft: Support for record extension. - changes to the Parser to support declaration of extended records - changes in the AST to represent extended records (base type, level) - changes to the type checker in Sema to support subtype polymorphism for records - changes to PrettyPrinter to show extended record information --- src/data/ast/NodePrettyPrinter.cpp | 10 ++++++-- src/data/ast/PointerTypeNode.cpp | 4 ++++ src/data/ast/PointerTypeNode.h | 2 ++ src/data/ast/RecordTypeNode.cpp | 16 ++++++++----- src/data/ast/RecordTypeNode.h | 9 +++++--- src/data/ast/TypeNode.cpp | 4 ++++ src/data/ast/TypeNode.h | 2 ++ src/sema/Sema.cpp | 37 ++++++++++++++++-------------- test/oberon/RecordExt.Mod | 11 +++++---- 9 files changed, 62 insertions(+), 33 deletions(-) diff --git a/src/data/ast/NodePrettyPrinter.cpp b/src/data/ast/NodePrettyPrinter.cpp index 2aa7d1c..283482e 100644 --- a/src/data/ast/NodePrettyPrinter.cpp +++ b/src/data/ast/NodePrettyPrinter.cpp @@ -178,8 +178,9 @@ void NodePrettyPrinter::selectors(std::vector> &selectors) } stream_ << "]"; } else if (type == NodeType::record_type) { + auto field = dynamic_cast(selector)->getField(); stream_ << "."; - stream_ << *dynamic_cast(selector)->getField()->getIdentifier(); + stream_ << field->getIdentifier()->name(); } else if (type == NodeType::pointer_type) { stream_ << "^"; } else if (type == NodeType::type) { @@ -347,7 +348,12 @@ void NodePrettyPrinter::visit([[maybe_unused]] ProcedureTypeNode &node) { void NodePrettyPrinter::visit(RecordTypeNode &node) { if (node.isAnonymous() || isDecl_) { isDecl_ = false; - stream_ << "RECORD "; + stream_ << "RECORD(*Level:" << node.level() << "*) "; + if (node.isExtened()) { + stream_ << "("; + node.getBaseType()->accept(*this); + stream_ << ") "; + } for (size_t i = 0; i < node.getFieldCount(); i++) { node.getField(i)->accept(*this); if (i + 1 < node.getFieldCount()) { diff --git a/src/data/ast/PointerTypeNode.cpp b/src/data/ast/PointerTypeNode.cpp index ffbf1f5..c38f644 100644 --- a/src/data/ast/PointerTypeNode.cpp +++ b/src/data/ast/PointerTypeNode.cpp @@ -13,6 +13,10 @@ TypeNode *PointerTypeNode::getBase() const { return base_; } +bool PointerTypeNode::extends(TypeNode *base) const { + return base_->extends(dynamic_cast(base)->getBase()); +} + void PointerTypeNode::accept(NodeVisitor &visitor) { visitor.visit(*this); } diff --git a/src/data/ast/PointerTypeNode.h b/src/data/ast/PointerTypeNode.h index 44ff12a..9926fcd 100644 --- a/src/data/ast/PointerTypeNode.h +++ b/src/data/ast/PointerTypeNode.h @@ -21,6 +21,8 @@ class PointerTypeNode final : public TypeNode { void setBase(TypeNode *base); [[nodiscard]] TypeNode *getBase() const; + [[nodiscard]] bool extends(TypeNode *) const override; + void accept(NodeVisitor &visitor) final; void print(std::ostream &stream) const final; diff --git a/src/data/ast/RecordTypeNode.cpp b/src/data/ast/RecordTypeNode.cpp index a89ff25..5f09e3c 100644 --- a/src/data/ast/RecordTypeNode.cpp +++ b/src/data/ast/RecordTypeNode.cpp @@ -32,19 +32,23 @@ size_t RecordTypeNode::getFieldCount() { return fields_.size(); } +RecordTypeNode *RecordTypeNode::getBaseType() const { + return base_; +} + bool RecordTypeNode::isExtened() const { return base_ != nullptr; } -bool RecordTypeNode::instanceOf(RecordTypeNode *type) const { - if (this != type && base_) { - return base_->instanceOf(type); +bool RecordTypeNode::extends(TypeNode *base) const { + if (this != base && base_) { + return base_->extends(base); } - return this == type; + return this == base; } -RecordTypeNode *RecordTypeNode::getBaseType() const { - return base_; +unsigned short RecordTypeNode::level() const { + return level_; } void RecordTypeNode::accept(NodeVisitor &visitor) { diff --git a/src/data/ast/RecordTypeNode.h b/src/data/ast/RecordTypeNode.h index 6f9e7e1..5f767b3 100644 --- a/src/data/ast/RecordTypeNode.h +++ b/src/data/ast/RecordTypeNode.h @@ -26,11 +26,12 @@ class RecordTypeNode final : public TypeNode { private: vector> fields_; RecordTypeNode *base_; + unsigned short level_; public: RecordTypeNode(const FilePos &pos, Ident *ident, RecordTypeNode *base, vector> fields) : TypeNode(NodeType::record_type, pos, ident, TypeKind::RECORD, 0), - fields_(std::move(fields)), base_(base) {}; + fields_(std::move(fields)), base_(base), level_(base ? base->level() + 1 : 0) {}; ~RecordTypeNode() final = default; [[nodiscard]] unsigned int getSize() const final; @@ -39,9 +40,11 @@ class RecordTypeNode final : public TypeNode { [[nodiscard]] FieldNode *getField(size_t num) const; [[nodiscard]] size_t getFieldCount(); - [[nodiscard]] bool isExtened() const; - [[nodiscard]] bool instanceOf(RecordTypeNode *) const; [[nodiscard]] RecordTypeNode *getBaseType() const; + [[nodiscard]] bool isExtened() const; + [[nodiscard]] bool extends(TypeNode *) const override; + + [[nodiscard]] unsigned short level() const; void accept(NodeVisitor &visitor) final; void print(ostream &out) const final; diff --git a/src/data/ast/TypeNode.cpp b/src/data/ast/TypeNode.cpp index a947f60..6f825c0 100644 --- a/src/data/ast/TypeNode.cpp +++ b/src/data/ast/TypeNode.cpp @@ -110,6 +110,10 @@ bool TypeNode::isBasic() const { return isBoolean() || isNumeric() || isSet(); } +bool TypeNode::extends([[maybe_unused]] TypeNode *base) const { + return false; +} + void TypeNode::setRef(int ref) { ref_ = ref; } diff --git a/src/data/ast/TypeNode.h b/src/data/ast/TypeNode.h index cb32d42..191d124 100644 --- a/src/data/ast/TypeNode.h +++ b/src/data/ast/TypeNode.h @@ -61,6 +61,8 @@ class TypeNode : public Node { [[nodiscard]] bool isNumeric() const; [[nodiscard]] bool isStructured() const; + [[nodiscard]] virtual bool extends(TypeNode *) const; + void setRef(int); [[nodiscard]] int getRef() const; diff --git a/src/sema/Sema.cpp b/src/sema/Sema.cpp index f947926..37e67a0 100644 --- a/src/sema/Sema.cpp +++ b/src/sema/Sema.cpp @@ -862,32 +862,26 @@ TypeNode *Sema::onTypeguard(DeclarationNode *sym, [[maybe_unused]] TypeNode *bas auto decl = symbols_->lookup(sel->ident()); if (decl) { // O07.8.1: in v(T), v is a variable parameter of record type, or v is a pointer. - if ((sym->getNodeType() == NodeType::parameter && dynamic_cast(sym)->isVar() && - sym->getType()->isRecord()) || sym->getType()->isPointer()) { + if (sym->getType()->isPointer() || (sym->getType()->isRecord() && + sym->getNodeType() == NodeType::parameter && + dynamic_cast(sym)->isVar())) { + TypeNode *actual = sym->getType(); if (decl->getNodeType() == NodeType::type) { - RecordTypeNode *actual; - auto type = dynamic_cast(decl)->getType(); - if (type->isPointer()) { - type = dynamic_cast(type)->getBase(); - } - if (type->isRecord()) { - actual = dynamic_cast(sym->getType()); - auto guard = dynamic_cast(type); - if (guard->instanceOf(actual)) { - return guard; - } else { + auto guard = dynamic_cast(decl)->getType(); + if (guard->isPointer() || guard->isRecord()) { + if (!guard->extends(actual)) { logger_.error(start, "type mismatch: " + format(guard) + " is not an extension of " - + format(actual) + "."); + + format(actual) + "."); } } else { - logger_.error(start, "record type or pointer to record type expected."); + logger_.error(start, "type mismatch: record type or pointer to record type expected."); } - return type; + return guard; } else { logger_.error(start, "unexpected selector."); } } else { - logger_.error(start, "a type guard can only be applied to a variable parameter of record type or a pointer."); + logger_.error(start, "type mismatch: a type guard can only be applied to a variable parameter of record type or a pointer."); } } else { logger_.error(start, "undefined identifier: " + to_string(*sel->ident()) + "."); @@ -1633,9 +1627,18 @@ Sema::assertCompatible(const FilePos &pos, TypeNode *expected, TypeNode *actual, } } } + // Check record type + if (expected->isRecord() && actual->isRecord()) { + if (var) { + return actual->extends(expected); + } + } // Check pointer type if (expected->isPointer()) { if (actual->isPointer()) { + if (actual->extends(expected)) { + return true; + } auto exp_ptr = dynamic_cast(expected); auto act_ptr = dynamic_cast(actual); return assertCompatible(pos, exp_ptr->getBase(), act_ptr->getBase(), var, true); diff --git a/test/oberon/RecordExt.Mod b/test/oberon/RecordExt.Mod index 928315f..2a7ad98 100644 --- a/test/oberon/RecordExt.Mod +++ b/test/oberon/RecordExt.Mod @@ -20,9 +20,9 @@ TYPE Shape = RECORD (ObjectDesc) area: REAL END; - Circle = RECORD (Shape) + Circle* = RECORD (Shape) centre: Point2D; - radius: REAL + radius*: REAL END; Sphere = RECORD (ObjectDesc) centre: Point3D; @@ -36,15 +36,16 @@ VAR nd: IntNodeDesc; PROCEDURE Init(VAR c: Shape; r: REAL); BEGIN - c(Circle).radius := r; - c(Sphere).radius := r + c(Circle).radius := r + (*c(Sphere).radius := r*) END Init; BEGIN - nd.int := 0; + nd.val := 0; Init(circ, 3.141); Out.Real(circ.radius, 0); Out.Ln; NEW(inode); node := inode; + (*inode := node;*) node(IntNode).val := 42 END RecordExt. \ No newline at end of file From 2c868062080a27f80840e898684947ba667c4f80 Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Sun, 21 Apr 2024 16:27:29 +0200 Subject: [PATCH 04/10] Draft: Support for record extension. - changes to the Parser to support declaration of extended records - changes in the AST to represent extended records (base type, level) - changes to the type checker in Sema to support subtype polymorphism for records - changes to PrettyPrinter to show extended record information - update to `.gitignore` to ignore files generated by OBNC --- .gitignore | 1 + test/oberon/.gitignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4d705be..74b4205 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ # Oberon *.smb *.sym +**/.obnc/ # macOS *.DS_Store diff --git a/test/oberon/.gitignore b/test/oberon/.gitignore index 82b9a85..d9864c3 100644 --- a/test/oberon/.gitignore +++ b/test/oberon/.gitignore @@ -12,6 +12,7 @@ # ignore files generated by the Oberon compiler *.smb *.sym +/.obnc/ # macOS *.DS_Store From 7816a268c7b808a07acb57625e2221060a45b6e0 Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Thu, 14 Mar 2024 16:50:47 +0100 Subject: [PATCH 05/10] Draft: Support for record extension. --- src/codegen/llvm/LLVMIRBuilder.cpp | 9 ++++- src/data/ast/RecordTypeNode.cpp | 11 +++++- src/data/ast/RecordTypeNode.h | 7 ++-- src/data/symtab/SymbolImporter.cpp | 2 +- src/parser/Parser.cpp | 23 +++++++---- src/sema/Sema.cpp | 49 ++++++++++++++++++++--- src/sema/Sema.h | 2 +- test/c/Arrays.c | 63 +++++++++++++----------------- test/oberon/RecordExt.Mod | 42 ++++++++++++++++++++ 9 files changed, 150 insertions(+), 58 deletions(-) create mode 100644 test/oberon/RecordExt.Mod diff --git a/src/codegen/llvm/LLVMIRBuilder.cpp b/src/codegen/llvm/LLVMIRBuilder.cpp index 675e850..ea27191 100644 --- a/src/codegen/llvm/LLVMIRBuilder.cpp +++ b/src/codegen/llvm/LLVMIRBuilder.cpp @@ -306,13 +306,18 @@ TypeNode *LLVMIRBuilder::selectors(TypeNode *base, SelectorIterator start, Selec // handle record field access auto field = dynamic_cast(sel)->getField(); auto record_t = dynamic_cast(selector_t); + auto recordTy = getLLVMType(record_t); + value = processGEP(base, value, indices); + // get address of first field + auto layout = module_->getDataLayout().getStructLayout((StructType *)recordTy); + auto offset = layout->getElementOffset(0); for (size_t pos = 0; pos < record_t->getFieldCount(); pos++) { if (field == record_t->getField(pos)) { - indices.push_back(builder_.getInt32(pos)); + offset = layout->getElementOffset(pos) - offset; + value = builder_.CreateInBoundsGEP(builder_.getInt8Ty(), value, {builder_.getInt64(offset)}); break; } } - value = processGEP(base, value, indices); selector_t = field->getType(); base = selector_t; } else if (sel->getNodeType() == NodeType::type) { diff --git a/src/data/ast/RecordTypeNode.cpp b/src/data/ast/RecordTypeNode.cpp index e23b68a..a89ff25 100644 --- a/src/data/ast/RecordTypeNode.cpp +++ b/src/data/ast/RecordTypeNode.cpp @@ -32,8 +32,15 @@ size_t RecordTypeNode::getFieldCount() { return fields_.size(); } -void RecordTypeNode::setBaseType(RecordTypeNode *base) { - base_ = base; +bool RecordTypeNode::isExtened() const { + return base_ != nullptr; +} + +bool RecordTypeNode::instanceOf(RecordTypeNode *type) const { + if (this != type && base_) { + return base_->instanceOf(type); + } + return this == type; } RecordTypeNode *RecordTypeNode::getBaseType() const { diff --git a/src/data/ast/RecordTypeNode.h b/src/data/ast/RecordTypeNode.h index 7309c74..b5354e1 100644 --- a/src/data/ast/RecordTypeNode.h +++ b/src/data/ast/RecordTypeNode.h @@ -28,9 +28,9 @@ class RecordTypeNode final : public TypeNode { RecordTypeNode *base_; public: - RecordTypeNode(const FilePos &pos, vector> fields) : + RecordTypeNode(const FilePos &pos, RecordTypeNode *base, vector> fields) : TypeNode(NodeType::record_type, pos, TypeKind::RECORD, 0), - fields_(std::move(fields)), base_() {}; + fields_(std::move(fields)), base_(base) {}; ~RecordTypeNode() final = default; [[nodiscard]] unsigned int getSize() const final; @@ -39,7 +39,8 @@ class RecordTypeNode final : public TypeNode { [[nodiscard]] FieldNode *getField(size_t num) const; [[nodiscard]] size_t getFieldCount(); - void setBaseType(RecordTypeNode *base); + [[nodiscard]] bool isExtened() const; + [[nodiscard]] bool instanceOf(RecordTypeNode *) const; [[nodiscard]] RecordTypeNode *getBaseType() const; void accept(NodeVisitor &visitor) final; diff --git a/src/data/symtab/SymbolImporter.cpp b/src/data/symtab/SymbolImporter.cpp index 94de934..b38dc3b 100644 --- a/src/data/symtab/SymbolImporter.cpp +++ b/src/data/symtab/SymbolImporter.cpp @@ -303,7 +303,7 @@ TypeNode *SymbolImporter::readRecordType(SymbolFile *file) { // check for terminator ch = file->readChar(); } - auto res = context_->getOrInsertRecordType(EMPTY_POS, EMPTY_POS, std::move(fields)); + auto res = context_->getOrInsertRecordType(EMPTY_POS, EMPTY_POS, base_t, std::move(fields)); res->setBaseType(base_t); res->setSize(size); return res; diff --git a/src/parser/Parser.cpp b/src/parser/Parser.cpp index 53430a2..9b77ef3 100644 --- a/src/parser/Parser.cpp +++ b/src/parser/Parser.cpp @@ -294,23 +294,32 @@ ArrayTypeNode* Parser::array_type() { return nullptr; } -// TODO record_type = "RECORD" [ "(" qualident ")" ] [ field_list { ";" field_list } ] END. -// record_type = "RECORD" field_list { ";" field_list } "END" . -RecordTypeNode* Parser::record_type() { +// record_type = "RECORD" [ "(" qualident ")" ] [ field_list { ";" field_list } ] END. +RecordTypeNode* Parser::record_type(Ident* identifier) { logger_.debug("record_type"); FilePos pos = scanner_.next()->start(); // skip RECORD keyword and get its position + unique_ptr base; + if (scanner_.peek()->type() == TokenType::lparen) { + scanner_.next(); // skip left parenthesis + base = qualident(); + if (assertToken(scanner_.peek(), TokenType::rparen)) { + scanner_.next(); // skip right parenthesis + } + } vector> fields; - field_list(fields); - while (scanner_.peek()->type() == TokenType::semicolon) { - scanner_.next(); + if (scanner_.peek()->type() == TokenType::const_ident) { field_list(fields); + while (scanner_.peek()->type() == TokenType::semicolon) { + scanner_.next(); // skip semicolon + field_list(fields); + } } if (assertToken(scanner_.peek(), TokenType::kw_end)) { scanner_.next(); } // [<)>, <;>, ] // resync({ TokenType::semicolon, TokenType::rparen, TokenType::kw_end }); - return sema_.onRecordType(pos, EMPTY_POS, std::move(fields)); + return sema_.onRecordType(pos, EMPTY_POS, identifier, std::move(base), std::move(fields)); } // field_list = ident_list ":" type . diff --git a/src/sema/Sema.cpp b/src/sema/Sema.cpp index c759104..bafc900 100644 --- a/src/sema/Sema.cpp +++ b/src/sema/Sema.cpp @@ -253,11 +253,33 @@ Sema::onParameter(const FilePos &start, [[maybe_unused]] const FilePos &end, } RecordTypeNode * -Sema::onRecordType(const FilePos &start, const FilePos &end, vector> fields) { - auto node = context_->getOrInsertRecordType(start, end, std::move(fields)); +Sema::onRecordType(const FilePos &start, const FilePos &end, + unique_ptr ident, vector> fields) { + RecordTypeNode *base = nullptr; + if (ident) { + auto sym = symbols_->lookup(ident.get()); + if (sym && sym->getNodeType() == NodeType::type) { + auto type = dynamic_cast(sym)->getType(); + if (type->isRecord()) { + base = dynamic_cast(type); + } else { + logger_.error(ident->start(), "base type must be a record type."); + } + } else { + logger_.error(start, "undefined type: " + to_string(*ident) + "."); + } + } + auto node = context_->getOrInsertRecordType(start, end, base, std::move(fields)); set names; for (size_t i = 0; i < node->getFieldCount(); i++) { auto field = node->getField(i); + if (field->getIdentifier()->isExported()) { + if (symbols_->getLevel() != SymbolTable::MODULE_LEVEL) { + logger_.error(field->pos(), "only top-level declarations can be exported."); + } else if (!node->getIdentifier()->isExported()) { + logger_.error(field->pos(), "cannot export fields of non-exported record type."); + } + } if (names.count(field->getIdentifier()->name())) { logger_.error(field->pos(), "duplicate record field: " + to_string(*field->getIdentifier()) + "."); } else { @@ -869,14 +891,29 @@ FieldNode *Sema::onRecordField(TypeNode *base, RecordField *sel) { } TypeNode *Sema::onTypeguard(DeclarationNode *sym, [[maybe_unused]] TypeNode *base, Typeguard *sel) { - auto type = symbols_->lookup(sel->ident()); - if (type) { + auto decl = symbols_->lookup(sel->ident()); + if (decl) { // O07.8.1: in v(T), v is a variable parameter of record type, or v is a pointer. if ((sym->getNodeType() == NodeType::parameter && dynamic_cast(sym)->isVar() && sym->getType()->isRecord()) || sym->getType()->isPointer()) { - if (type->getNodeType() == NodeType::type) { + if (decl->getNodeType() == NodeType::type) { + auto type = dynamic_cast(decl)->getType(); // TODO check if type-guard is compatible with base type. - return dynamic_cast(type)->getType(); + if (type->isPointer()) { + type = dynamic_cast(type)->getBase(); + } + if (type->isRecord()) { + auto actual = dynamic_cast(sym->getType()); + auto guard = dynamic_cast(type); + if (guard->instanceOf(actual)) { + return guard; + } else { + logger_.error(sel->pos(), "type mismatch: " + format(guard) + " is not an extension of " + + format(actual) + "."); + } + } else { + logger_.error(sel->pos(), "record type or pointer to record type expected."); + } } else { logger_.error(sel->pos(), "unexpected selector."); } diff --git a/src/sema/Sema.h b/src/sema/Sema.h index 5ad5bfb..f8f4671 100644 --- a/src/sema/Sema.h +++ b/src/sema/Sema.h @@ -118,7 +118,7 @@ class Sema { vector>, bool varargs, TypeNode *); unique_ptr onParameter(const FilePos &, const FilePos &, unique_ptr, TypeNode *, bool, unsigned = 0); - RecordTypeNode *onRecordType(const FilePos &, const FilePos &, vector>); + RecordTypeNode *onRecordType(const FilePos &, const FilePos &, unique_ptr, vector>); unique_ptr onField(const FilePos&, const FilePos&, unique_ptr, TypeNode*, unsigned = 0); TypeNode *onTypeReference(const FilePos &, const FilePos &, unique_ptr, unsigned = 0); diff --git a/test/c/Arrays.c b/test/c/Arrays.c index defeb47..54c6507 100644 --- a/test/c/Arrays.c +++ b/test/c/Arrays.c @@ -4,6 +4,7 @@ #include #include +#include #include const int SIZE = 5000; @@ -11,48 +12,38 @@ const int MAXVAL = 10 * SIZE; int a[5000]; -struct Array { +typedef struct { int dim; - int vec[]; -}; + float vec[10]; + int test; +} Array; -void print(int value[]) { - for (int i = 0; i < SIZE; i++) { - printf("%d ", value[i]); - } -} - -void destroy(int value[]) { - for (int i = 0; i < SIZE; i++) { - value[i] = i; - } -} - -void Len(struct Array *arr) { - arr->dim = 100; - arr->vec[99] = 20; -} +//void print(int value[]) { +// for (int i = 0; i < SIZE; i++) { +// printf("%d ", value[i]); +// } +//} -struct Array getArray() { - struct Array s; - return s; -} +//void destroy(int value[]) { +// for (int i = 0; i < SIZE; i++) { +// value[i] = i; +// } +//} -void setArray(struct Array s) { - s.dim = 100; - s.vec[99] = 20; +void test(Array *rec) { + int test = *((int*)(((char*)rec) + (offsetof(Array, test) - offsetof(Array, dim)))); + printf("%d\n", test); } int main(int argc, const char* argv[]) { -// int a[SIZE]; -// srand(time(NULL)); -// for (int i = 0; i < SIZE; i++) { -// a[i] = rand() % 100 + 1; -// } -// print(a); -// destroy(a); -// print(a); - struct Array s; - setArray(s); + Array m[10]; + Array s; + s.dim = 10; + s.test = 20; + m[5] = s; + + int len = *((int*)(((char*)m[5].vec) + (offsetof(Array, dim) - offsetof(Array, vec)))); + printf("%d\n", len); + test(&s); return 0; } diff --git a/test/oberon/RecordExt.Mod b/test/oberon/RecordExt.Mod new file mode 100644 index 0000000..96a59ad --- /dev/null +++ b/test/oberon/RecordExt.Mod @@ -0,0 +1,42 @@ +MODULE RecordExt; +IMPORT Out; + +TYPE + ObjectDesc = RECORD END; + Node = POINTER TO NodeDesc; + NodeDesc = RECORD (ObjectDesc) + int: INTEGER; + next: Node + END; + Point2D = RECORD (ObjectDesc) + x, y: INTEGER + END; + Point3D = RECORD (Point2D) + z: INTEGER + END; + Shape = RECORD (ObjectDesc) + area: REAL + END; + Circle = RECORD (Shape) + centre: Point2D; + radius: REAL + END; + Sphere = RECORD (ObjectDesc) + centre: Point3D; + radius: REAL + END; + +VAR nd: NodeDesc; + circ: Circle; + +PROCEDURE Init(VAR c: Shape; r: REAL); +BEGIN + c(Circle).radius := r; + c(Sphere).radius := r +END Init; + +BEGIN + nd.int := 0; + Init(circ, 3.141); + Out.Real(circ.radius, 0); Out.Ln +END RecordExt. \ No newline at end of file From 6e80755782c73eef231cd780a998e71676193d6a Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Fri, 15 Mar 2024 07:03:22 +0100 Subject: [PATCH 06/10] Draft: Support for record extension. - changes to the Parser to support declaration of extended records - changes in the AST to represent extended records - changes to the type checker in Sema to support subtype polymorphism for records --- src/sema/Sema.cpp | 18 ++++++++++-------- test/oberon/RecordExt.Mod | 14 +++++++++++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/sema/Sema.cpp b/src/sema/Sema.cpp index bafc900..2035bfb 100644 --- a/src/sema/Sema.cpp +++ b/src/sema/Sema.cpp @@ -891,39 +891,41 @@ FieldNode *Sema::onRecordField(TypeNode *base, RecordField *sel) { } TypeNode *Sema::onTypeguard(DeclarationNode *sym, [[maybe_unused]] TypeNode *base, Typeguard *sel) { + FilePos start = sel->ident()->start(); auto decl = symbols_->lookup(sel->ident()); if (decl) { // O07.8.1: in v(T), v is a variable parameter of record type, or v is a pointer. if ((sym->getNodeType() == NodeType::parameter && dynamic_cast(sym)->isVar() && sym->getType()->isRecord()) || sym->getType()->isPointer()) { if (decl->getNodeType() == NodeType::type) { + RecordTypeNode *actual; auto type = dynamic_cast(decl)->getType(); - // TODO check if type-guard is compatible with base type. if (type->isPointer()) { type = dynamic_cast(type)->getBase(); } if (type->isRecord()) { - auto actual = dynamic_cast(sym->getType()); + actual = dynamic_cast(sym->getType()); auto guard = dynamic_cast(type); if (guard->instanceOf(actual)) { return guard; } else { - logger_.error(sel->pos(), "type mismatch: " + format(guard) + " is not an extension of " + logger_.error(start, "type mismatch: " + format(guard) + " is not an extension of " + format(actual) + "."); } } else { - logger_.error(sel->pos(), "record type or pointer to record type expected."); + logger_.error(start, "record type or pointer to record type expected."); } + return type; } else { - logger_.error(sel->pos(), "unexpected selector."); + logger_.error(start, "unexpected selector."); } } else { - logger_.error(sel->pos(), "a type guard can only be applied to a variable parameter of record type or a pointer."); + logger_.error(start, "a type guard can only be applied to a variable parameter of record type or a pointer."); } } else { - logger_.error(sel->pos(), "undefined identifier: " + to_string(*sel->ident()) + "."); + logger_.error(start, "undefined identifier: " + to_string(*sel->ident()) + "."); } - return nullptr; + return nullTy_; } unique_ptr diff --git a/test/oberon/RecordExt.Mod b/test/oberon/RecordExt.Mod index 96a59ad..928315f 100644 --- a/test/oberon/RecordExt.Mod +++ b/test/oberon/RecordExt.Mod @@ -5,9 +5,12 @@ TYPE ObjectDesc = RECORD END; Node = POINTER TO NodeDesc; NodeDesc = RECORD (ObjectDesc) - int: INTEGER; next: Node END; + IntNode = POINTER TO IntNodeDesc; + IntNodeDesc = RECORD (NodeDesc) + val: INTEGER + END; Point2D = RECORD (ObjectDesc) x, y: INTEGER END; @@ -26,8 +29,10 @@ TYPE radius: REAL END; -VAR nd: NodeDesc; +VAR nd: IntNodeDesc; circ: Circle; + node: Node; + inode: IntNode; PROCEDURE Init(VAR c: Shape; r: REAL); BEGIN @@ -38,5 +43,8 @@ END Init; BEGIN nd.int := 0; Init(circ, 3.141); - Out.Real(circ.radius, 0); Out.Ln + Out.Real(circ.radius, 0); Out.Ln; + NEW(inode); + node := inode; + node(IntNode).val := 42 END RecordExt. \ No newline at end of file From 144983e5e00f34679ef655b4303ec68c423a1925 Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Fri, 15 Mar 2024 13:38:51 +0100 Subject: [PATCH 07/10] Draft: Support for record extension. - changes to the Parser to support declaration of extended records - changes in the AST to represent extended records (base type, level) - changes to the type checker in Sema to support subtype polymorphism for records - changes to PrettyPrinter to show extended record information --- src/data/ast/NodePrettyPrinter.cpp | 10 ++++++-- src/data/ast/PointerTypeNode.cpp | 4 ++++ src/data/ast/PointerTypeNode.h | 2 ++ src/data/ast/RecordTypeNode.cpp | 16 ++++++++----- src/data/ast/RecordTypeNode.h | 9 +++++--- src/data/ast/TypeNode.cpp | 4 ++++ src/data/ast/TypeNode.h | 2 ++ src/sema/Sema.cpp | 37 ++++++++++++++++-------------- test/oberon/RecordExt.Mod | 11 +++++---- 9 files changed, 62 insertions(+), 33 deletions(-) diff --git a/src/data/ast/NodePrettyPrinter.cpp b/src/data/ast/NodePrettyPrinter.cpp index e9637af..970d7c9 100644 --- a/src/data/ast/NodePrettyPrinter.cpp +++ b/src/data/ast/NodePrettyPrinter.cpp @@ -188,8 +188,9 @@ void NodePrettyPrinter::selectors(std::vector> &selectors) } stream_ << "]"; } else if (type == NodeType::record_type) { + auto field = dynamic_cast(selector)->getField(); stream_ << "."; - stream_ << *dynamic_cast(selector)->getField()->getIdentifier(); + stream_ << field->getIdentifier()->name(); } else if (type == NodeType::pointer_type) { stream_ << "^"; } else if (type == NodeType::type) { @@ -357,7 +358,12 @@ void NodePrettyPrinter::visit([[maybe_unused]] ProcedureTypeNode &node) { void NodePrettyPrinter::visit(RecordTypeNode &node) { if (node.isAnonymous() || isDecl_) { isDecl_ = false; - stream_ << "RECORD "; + stream_ << "RECORD(*Level:" << node.level() << "*) "; + if (node.isExtened()) { + stream_ << "("; + node.getBaseType()->accept(*this); + stream_ << ") "; + } for (size_t i = 0; i < node.getFieldCount(); i++) { node.getField(i)->accept(*this); if (i + 1 < node.getFieldCount()) { diff --git a/src/data/ast/PointerTypeNode.cpp b/src/data/ast/PointerTypeNode.cpp index ffbf1f5..c38f644 100644 --- a/src/data/ast/PointerTypeNode.cpp +++ b/src/data/ast/PointerTypeNode.cpp @@ -13,6 +13,10 @@ TypeNode *PointerTypeNode::getBase() const { return base_; } +bool PointerTypeNode::extends(TypeNode *base) const { + return base_->extends(dynamic_cast(base)->getBase()); +} + void PointerTypeNode::accept(NodeVisitor &visitor) { visitor.visit(*this); } diff --git a/src/data/ast/PointerTypeNode.h b/src/data/ast/PointerTypeNode.h index 7837314..fec1412 100644 --- a/src/data/ast/PointerTypeNode.h +++ b/src/data/ast/PointerTypeNode.h @@ -21,6 +21,8 @@ class PointerTypeNode final : public TypeNode { void setBase(TypeNode *base); [[nodiscard]] TypeNode *getBase() const; + [[nodiscard]] bool extends(TypeNode *) const override; + void accept(NodeVisitor &visitor) final; void print(std::ostream &stream) const final; diff --git a/src/data/ast/RecordTypeNode.cpp b/src/data/ast/RecordTypeNode.cpp index a89ff25..5f09e3c 100644 --- a/src/data/ast/RecordTypeNode.cpp +++ b/src/data/ast/RecordTypeNode.cpp @@ -32,19 +32,23 @@ size_t RecordTypeNode::getFieldCount() { return fields_.size(); } +RecordTypeNode *RecordTypeNode::getBaseType() const { + return base_; +} + bool RecordTypeNode::isExtened() const { return base_ != nullptr; } -bool RecordTypeNode::instanceOf(RecordTypeNode *type) const { - if (this != type && base_) { - return base_->instanceOf(type); +bool RecordTypeNode::extends(TypeNode *base) const { + if (this != base && base_) { + return base_->extends(base); } - return this == type; + return this == base; } -RecordTypeNode *RecordTypeNode::getBaseType() const { - return base_; +unsigned short RecordTypeNode::level() const { + return level_; } void RecordTypeNode::accept(NodeVisitor &visitor) { diff --git a/src/data/ast/RecordTypeNode.h b/src/data/ast/RecordTypeNode.h index b5354e1..0bcee39 100644 --- a/src/data/ast/RecordTypeNode.h +++ b/src/data/ast/RecordTypeNode.h @@ -26,11 +26,12 @@ class RecordTypeNode final : public TypeNode { private: vector> fields_; RecordTypeNode *base_; + unsigned short level_; public: RecordTypeNode(const FilePos &pos, RecordTypeNode *base, vector> fields) : TypeNode(NodeType::record_type, pos, TypeKind::RECORD, 0), - fields_(std::move(fields)), base_(base) {}; + fields_(std::move(fields)), base_(base), level_(base ? base->level() + 1 : 0) {}; ~RecordTypeNode() final = default; [[nodiscard]] unsigned int getSize() const final; @@ -39,9 +40,11 @@ class RecordTypeNode final : public TypeNode { [[nodiscard]] FieldNode *getField(size_t num) const; [[nodiscard]] size_t getFieldCount(); - [[nodiscard]] bool isExtened() const; - [[nodiscard]] bool instanceOf(RecordTypeNode *) const; [[nodiscard]] RecordTypeNode *getBaseType() const; + [[nodiscard]] bool isExtened() const; + [[nodiscard]] bool extends(TypeNode *) const override; + + [[nodiscard]] unsigned short level() const; void accept(NodeVisitor &visitor) final; void print(ostream &out) const final; diff --git a/src/data/ast/TypeNode.cpp b/src/data/ast/TypeNode.cpp index c5360c3..e8d1eb4 100644 --- a/src/data/ast/TypeNode.cpp +++ b/src/data/ast/TypeNode.cpp @@ -133,6 +133,10 @@ bool TypeNode::isBasic() const { return isBoolean() || isNumeric() || isSet(); } +bool TypeNode::extends([[maybe_unused]] TypeNode *base) const { + return false; +} + void TypeNode::setRef(int ref) { ref_ = ref; } diff --git a/src/data/ast/TypeNode.h b/src/data/ast/TypeNode.h index 3ba6c7c..0a9a74f 100644 --- a/src/data/ast/TypeNode.h +++ b/src/data/ast/TypeNode.h @@ -74,6 +74,8 @@ class TypeNode : public Node { [[nodiscard]] bool isVirtual() const; + [[nodiscard]] virtual bool extends(TypeNode *) const; + void setRef(int); [[nodiscard]] int getRef() const; diff --git a/src/sema/Sema.cpp b/src/sema/Sema.cpp index 2035bfb..1e925b0 100644 --- a/src/sema/Sema.cpp +++ b/src/sema/Sema.cpp @@ -895,32 +895,26 @@ TypeNode *Sema::onTypeguard(DeclarationNode *sym, [[maybe_unused]] TypeNode *bas auto decl = symbols_->lookup(sel->ident()); if (decl) { // O07.8.1: in v(T), v is a variable parameter of record type, or v is a pointer. - if ((sym->getNodeType() == NodeType::parameter && dynamic_cast(sym)->isVar() && - sym->getType()->isRecord()) || sym->getType()->isPointer()) { + if (sym->getType()->isPointer() || (sym->getType()->isRecord() && + sym->getNodeType() == NodeType::parameter && + dynamic_cast(sym)->isVar())) { + TypeNode *actual = sym->getType(); if (decl->getNodeType() == NodeType::type) { - RecordTypeNode *actual; - auto type = dynamic_cast(decl)->getType(); - if (type->isPointer()) { - type = dynamic_cast(type)->getBase(); - } - if (type->isRecord()) { - actual = dynamic_cast(sym->getType()); - auto guard = dynamic_cast(type); - if (guard->instanceOf(actual)) { - return guard; - } else { + auto guard = dynamic_cast(decl)->getType(); + if (guard->isPointer() || guard->isRecord()) { + if (!guard->extends(actual)) { logger_.error(start, "type mismatch: " + format(guard) + " is not an extension of " - + format(actual) + "."); + + format(actual) + "."); } } else { - logger_.error(start, "record type or pointer to record type expected."); + logger_.error(start, "type mismatch: record type or pointer to record type expected."); } - return type; + return guard; } else { logger_.error(start, "unexpected selector."); } } else { - logger_.error(start, "a type guard can only be applied to a variable parameter of record type or a pointer."); + logger_.error(start, "type mismatch: a type guard can only be applied to a variable parameter of record type or a pointer."); } } else { logger_.error(start, "undefined identifier: " + to_string(*sel->ident()) + "."); @@ -1725,9 +1719,18 @@ Sema::assertCompatible(const FilePos &pos, TypeNode *expected, TypeNode *actual, } } } + // Check record type + if (expected->isRecord() && actual->isRecord()) { + if (var) { + return actual->extends(expected); + } + } // Check pointer type if (expected->isPointer()) { if (actual->isPointer()) { + if (actual->extends(expected)) { + return true; + } auto exp_ptr = dynamic_cast(expected); auto act_ptr = dynamic_cast(actual); return assertCompatible(pos, exp_ptr->getBase(), act_ptr->getBase(), var, true); diff --git a/test/oberon/RecordExt.Mod b/test/oberon/RecordExt.Mod index 928315f..2a7ad98 100644 --- a/test/oberon/RecordExt.Mod +++ b/test/oberon/RecordExt.Mod @@ -20,9 +20,9 @@ TYPE Shape = RECORD (ObjectDesc) area: REAL END; - Circle = RECORD (Shape) + Circle* = RECORD (Shape) centre: Point2D; - radius: REAL + radius*: REAL END; Sphere = RECORD (ObjectDesc) centre: Point3D; @@ -36,15 +36,16 @@ VAR nd: IntNodeDesc; PROCEDURE Init(VAR c: Shape; r: REAL); BEGIN - c(Circle).radius := r; - c(Sphere).radius := r + c(Circle).radius := r + (*c(Sphere).radius := r*) END Init; BEGIN - nd.int := 0; + nd.val := 0; Init(circ, 3.141); Out.Real(circ.radius, 0); Out.Ln; NEW(inode); node := inode; + (*inode := node;*) node(IntNode).val := 42 END RecordExt. \ No newline at end of file From 80b15c15c52adb475f30787a0961c1c006045391 Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Sun, 21 Apr 2024 16:27:29 +0200 Subject: [PATCH 08/10] Draft: Support for record extension. - changes to the Parser to support declaration of extended records - changes in the AST to represent extended records (base type, level) - changes to the type checker in Sema to support subtype polymorphism for records - changes to PrettyPrinter to show extended record information - update to `.gitignore` to ignore files generated by OBNC --- .gitignore | 1 + test/oberon/.gitignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4d705be..74b4205 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ # Oberon *.smb *.sym +**/.obnc/ # macOS *.DS_Store diff --git a/test/oberon/.gitignore b/test/oberon/.gitignore index 82b9a85..d9864c3 100644 --- a/test/oberon/.gitignore +++ b/test/oberon/.gitignore @@ -12,6 +12,7 @@ # ignore files generated by the Oberon compiler *.smb *.sym +/.obnc/ # macOS *.DS_Store From 9bfa152453c8ea213ee9bda9803e6ade0d2c4abd Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Wed, 8 May 2024 09:04:08 +0200 Subject: [PATCH 09/10] Draft: Support for record extension. - changes to the Parser to support declaration of extended records - changes in the AST to represent extended records (base type, level) - changes to the type checker in Sema to support subtype polymorphism for records - changes to PrettyPrinter to show extended record information - update to `.gitignore` to ignore files generated by OBNC --- src/codegen/llvm/LLVMIRBuilder.cpp | 33 +++++++++++++----- src/codegen/llvm/LLVMIRBuilder.h | 4 ++- src/sema/Sema.cpp | 20 +++++++++-- src/sema/Sema.h | 2 ++ test/oberon/RecordExt.Mod | 54 +++++++++++++++++++----------- 5 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/codegen/llvm/LLVMIRBuilder.cpp b/src/codegen/llvm/LLVMIRBuilder.cpp index ea27191..f596c09 100644 --- a/src/codegen/llvm/LLVMIRBuilder.cpp +++ b/src/codegen/llvm/LLVMIRBuilder.cpp @@ -16,21 +16,22 @@ using std::vector; LLVMIRBuilder::LLVMIRBuilder(CompilerConfig &config, LLVMContext &builder, Module *module) : NodeVisitor(), config_(config), logger_(config_.logger()), builder_(builder), module_(module), - value_(), values_(), types_(), hasArray_(), functions_(), strings_(), deref_ctx(), level_(0), - function_(), attrs_(AttrBuilder(builder)) { + value_(), values_(), types_(), leafTypes_(), hasArray_(), functions_(), strings_(), deref_ctx(), + level_(0), function_(), attrs_(AttrBuilder(builder)) { attrs_ - .addAttribute(Attribute::NoInline) - .addAttribute(Attribute::NoUnwind) - .addAttribute(Attribute::OptimizeNone) + .addAttribute(Attribute::NoInline) + .addAttribute(Attribute::NoUnwind) + .addAttribute(Attribute::OptimizeNone) #ifndef _LLVM_LEGACY - .addAttribute(Attribute::getWithUWTableKind(builder, UWTableKind::Default)) + .addAttribute(Attribute::getWithUWTableKind(builder, UWTableKind::Default)) #endif - ; + ; #ifndef __MINGW32__ if (!config_.hasFlag(Flag::NO_STACK_PROTECT)) { attrs_.addAttribute(Attribute::StackProtect); } #endif + recordTdTy_ = StructType::create(builder_.getContext(), {builder_.getPtrTy(), builder_.getInt32Ty()}); } void LLVMIRBuilder::build(ASTContext *ast) { @@ -1583,10 +1584,10 @@ Value *LLVMIRBuilder::processGEP(TypeNode *base, Value *value, vector & return value; } -Type* LLVMIRBuilder::getLLVMType(TypeNode *type) { +Type* LLVMIRBuilder::getLLVMType(TypeNode *type, bool leaf) { Type* result = nullptr; if (type == nullptr) { - result = builder_.getVoidTy(); + return builder_.getVoidTy(); } else if (types_[type] != nullptr) { result = types_[type]; } else if (type->getNodeType() == NodeType::array_type) { @@ -1602,6 +1603,11 @@ Type* LLVMIRBuilder::getLLVMType(TypeNode *type) { types_[type] = structTy; vector elemTys; auto recordTy = dynamic_cast(type); + // add field for base record + if (recordTy->isExtened()) { + elemTys.push_back(getLLVMType(recordTy->getBaseType(), false)); + } + // add regular record fields for (size_t i = 0; i < recordTy->getFieldCount(); i++) { auto fieldTy = recordTy->getField(i)->getType(); elemTys.push_back(getLLVMType(fieldTy)); @@ -1646,6 +1652,15 @@ Type* LLVMIRBuilder::getLLVMType(TypeNode *type) { logger_.error(type->pos(), "cannot map " + to_string(type->kind()) + " to LLVM intermediate representation."); exit(1); } + if (leaf && type->getNodeType() == NodeType::record_type) { + auto leafType = leafTypes_[type]; + if (!leafType) { + leafType = StructType::create(builder_.getContext(), {builder_.getPtrTy(), result}); + leafType->setName(to_string(result->getStructName().data()) + ".leaf"); + leafTypes_[type] = leafType; + } + result = leafType; + } return result; } diff --git a/src/codegen/llvm/LLVMIRBuilder.h b/src/codegen/llvm/LLVMIRBuilder.h index 01df488..719cfd2 100644 --- a/src/codegen/llvm/LLVMIRBuilder.h +++ b/src/codegen/llvm/LLVMIRBuilder.h @@ -42,6 +42,7 @@ class LLVMIRBuilder final : private NodeVisitor { Value *value_; map values_; map types_; + map leafTypes_; unordered_set hasArray_; map functions_; map strings_; @@ -50,8 +51,9 @@ class LLVMIRBuilder final : private NodeVisitor { Function *function_; AttrBuilder attrs_; ASTContext *ast_; + Type *recordTdTy_; - Type *getLLVMType(TypeNode *type); + Type *getLLVMType(TypeNode *type, bool = true); MaybeAlign getLLVMAlign(TypeNode *type); Value *processGEP(TypeNode *, Value *, vector &); diff --git a/src/sema/Sema.cpp b/src/sema/Sema.cpp index 086359b..9452793 100644 --- a/src/sema/Sema.cpp +++ b/src/sema/Sema.cpp @@ -273,10 +273,15 @@ Sema::onRecordType(const FilePos &start, const FilePos &end, set names; for (size_t i = 0; i < node->getFieldCount(); i++) { auto field = node->getField(i); - if (names.count(field->getIdentifier()->name())) { + auto name = field->getIdentifier()->name(); + if (base && resolveRecordField(base, name)) { + logger_.error(field->pos(), "redefinition of record field: " + to_string(*field->getIdentifier()) + "."); + continue; + } + if (names.count(name)) { logger_.error(field->pos(), "duplicate record field: " + to_string(*field->getIdentifier()) + "."); } else { - names.insert(field->getIdentifier()->name()); + names.insert(name); } } return node; @@ -873,7 +878,7 @@ FieldNode *Sema::onRecordField(TypeNode *base, RecordField *sel) { } auto record = dynamic_cast(base); auto ref = dynamic_cast(sel); - auto field = record->getField(ref->ident()->name()); + auto field = resolveRecordField(record, ref->ident()->name()); if (!field) { logger_.error(ref->pos(), "undefined record field: " + to_string(*ref->ident()) + "."); return nullptr; @@ -1831,3 +1836,12 @@ Sema::intType(int64_t value) { } return integerTy_; } + +FieldNode * +Sema::resolveRecordField(RecordTypeNode *type, const std::string &name) { + auto field = type->getField(name); + if (!field && type->isExtened()) { + return resolveRecordField(type->getBaseType(), name); + } + return field; +} diff --git a/src/sema/Sema.h b/src/sema/Sema.h index f8f4671..8675302 100644 --- a/src/sema/Sema.h +++ b/src/sema/Sema.h @@ -60,6 +60,8 @@ class Sema { bool assertCompatible(const FilePos &, TypeNode *, TypeNode *, bool = false, bool = false); TypeNode *commonType(const FilePos &, TypeNode *, TypeNode *) const; + static FieldNode *resolveRecordField(RecordTypeNode *, const string&); + static string format(const TypeNode *, bool = false); static int64_t euclidean_mod(int64_t, int64_t); diff --git a/test/oberon/RecordExt.Mod b/test/oberon/RecordExt.Mod index 2a7ad98..7880187 100644 --- a/test/oberon/RecordExt.Mod +++ b/test/oberon/RecordExt.Mod @@ -2,8 +2,9 @@ MODULE RecordExt; IMPORT Out; TYPE + Object = POINTER TO ObjectDesc; ObjectDesc = RECORD END; - Node = POINTER TO NodeDesc; + (*Node = POINTER TO NodeDesc; NodeDesc = RECORD (ObjectDesc) next: Node END; @@ -16,36 +17,51 @@ TYPE END; Point3D = RECORD (Point2D) z: INTEGER - END; - Shape = RECORD (ObjectDesc) + END;*) + Shape = POINTER TO ShapeDesc; + ShapeDesc = RECORD (ObjectDesc) area: REAL END; - Circle* = RECORD (Shape) - centre: Point2D; + Circle = POINTER TO CircleDesc; + CircleDesc* = RECORD (ShapeDesc) + (*centre: Point2D;*) + x, y: INTEGER; radius*: REAL END; - Sphere = RECORD (ObjectDesc) + (*Sphere = RECORD (ObjectDesc) centre: Point3D; radius: REAL - END; + END;*) -VAR nd: IntNodeDesc; - circ: Circle; - node: Node; - inode: IntNode; +VAR (*nd: IntNodeDesc;*) + o: Object; + s: Shape; + c: Circle; + cd: CircleDesc; + (*node: Node; + inode: IntNode;*) PROCEDURE Init(VAR c: Shape; r: REAL); BEGIN - c(Circle).radius := r - (*c(Sphere).radius := r*) + (*c(Circle).radius := r;*) + (*c(Sphere).radius := r;*) END Init; BEGIN - nd.val := 0; - Init(circ, 3.141); - Out.Real(circ.radius, 0); Out.Ln; - NEW(inode); - node := inode; + (*nd.val := 0; + Init(c, 3.141);*) + NEW(c); + c.x := -1; + c.y := -1; + c.radius := 1.0; + c.area := 3.14159265359 * c.radius * c.radius; + Out.Int(c.x, 0); Out.Ln; + Out.Int(c.y, 0); Out.Ln; + Out.Real(c.radius, 0); Out.Ln; + Out.Real(c.area, 0); Out.Ln; + (*FREE(c);*) + (*NEW(inode); + node := inode;*) (*inode := node;*) - node(IntNode).val := 42 + (*node(IntNode).val := 42*) END RecordExt. \ No newline at end of file From 63282c73a902e3cdac297a29b661d8e84ebf0d2a Mon Sep 17 00:00:00 2001 From: Michael Grossniklaus Date: Mon, 13 May 2024 07:03:01 +0200 Subject: [PATCH 10/10] Draft: Support for record extension. - changes to the Parser to support declaration of extended records - changes in the AST to represent extended records (base type, level) - changes to the type checker in Sema to support subtype polymorphism for records - changes to PrettyPrinter to show extended record information - update to `.gitignore` to ignore files generated by OBNC --- src/analyzer/LambdaLifter.cpp | 32 +++++++++---------- src/analyzer/LambdaLifter.h | 4 +-- src/codegen/llvm/LLVMIRBuilder.cpp | 50 +++++++++++++++++------------- src/codegen/llvm/LLVMIRBuilder.h | 2 +- src/data/ast/DeclarationNode.cpp | 27 ++++++++++++---- src/data/ast/DeclarationNode.h | 45 +++++++++++++++++++-------- src/data/ast/NodePrettyPrinter.cpp | 16 +++++----- src/data/ast/RecordTypeNode.cpp | 17 ++++++++-- src/data/ast/RecordTypeNode.h | 8 ++--- src/data/symtab/SymbolExporter.cpp | 4 +-- src/data/symtab/SymbolImporter.cpp | 2 +- src/data/symtab/SymbolTable.cpp | 14 ++++----- src/data/symtab/SymbolTable.h | 4 +-- src/sema/Sema.cpp | 29 ++++++----------- src/sema/Sema.h | 2 -- src/system/OberonSystem.cpp | 2 +- test/oberon/RecordExt.Mod | 36 ++++++++++++++------- 17 files changed, 174 insertions(+), 120 deletions(-) diff --git a/src/analyzer/LambdaLifter.cpp b/src/analyzer/LambdaLifter.cpp index dac19e7..532b7fa 100644 --- a/src/analyzer/LambdaLifter.cpp +++ b/src/analyzer/LambdaLifter.cpp @@ -38,7 +38,7 @@ void LambdaLifter::visit(ModuleNode &node) { void LambdaLifter::visit(ProcedureNode &node) { env_ = nullptr; - level_ = node.getLevel(); + scope_ = node.getScope(); path_.push_back(node.getIdentifier()->name()); bool is_super = node.getProcedureCount(); if (is_super) /* super procedure */ { @@ -58,17 +58,17 @@ void LambdaLifter::visit(ProcedureNode &node) { auto type = context_->getOrInsertRecordType(EMPTY_POS, EMPTY_POS, nullptr, std::move(fields)); auto decl = make_unique(EMPTY_POS, std::move(identifier), type); decl->setModule(module_); - decl->setLevel(module_->getLevel() + 1); + decl->setScope(module_->getScope() + 1); module_->types().push_back(std::move(decl)); // insert an additional formal parameter to the sub-procedures of the procedure to pass its environment for (auto &proc : node.procedures()) { auto param = make_unique(EMPTY_POS, make_unique(SUPER_), type, true); - param->setLevel(proc->getLevel() + 1); + param->setScope(proc->getScope() + 1); proc->getType()->parameters().push_back(std::move(param)); } // create local variable to manage for the procedure's environment (this) auto var = make_unique(EMPTY_POS, make_unique(THIS_), type); - var->setLevel(level_ + 1); + var->setScope(scope_ + 1); env_ = var.get(); // initialize the procedure's environment (this) for (size_t i = 0; i < node.getType()->parameters().size(); i++) { @@ -97,7 +97,7 @@ void LambdaLifter::visit(ProcedureNode &node) { } } // write updates back to super-procedure's environment (super := this.super) - if (level_ > SymbolTable::MODULE_LEVEL) /* neither root, nor leaf procedure */ { + if (scope_ > SymbolTable::MODULE_SCOPE) /* neither root, nor leaf procedure */ { auto param = findParameter(SUPER_, node.getType()->parameters()); if (param) { auto lhs = make_unique(param); @@ -122,17 +122,17 @@ void LambdaLifter::visit(ProcedureNode &node) { } // TODO remove unnecessary local variables // node.removeVariables(1, node.getVariableCount()); - } else if (level_ > SymbolTable::MODULE_LEVEL) /* leaf procedure */ { + } else if (scope_ > SymbolTable::MODULE_SCOPE) /* leaf procedure */ { if ((env_ = findParameter(SUPER_, node.getType()->parameters()))) { for (size_t i = 0; i < node.statements()->getStatementCount(); i++) { node.statements()->getStatement(i)->accept(*this); } } } - if (level_ > SymbolTable::MODULE_LEVEL) { - level_ = module_->getLevel() + 1; - node.setLevel(level_); - level_++; + if (scope_ > SymbolTable::MODULE_SCOPE) { + scope_ = module_->getScope() + 1; + node.setScope(scope_); + scope_++; for (auto ¶m : node.getType()->parameters()) { param->accept(*this); } @@ -161,17 +161,17 @@ ParameterNode *LambdaLifter::findParameter(string name, vectorgetType(), node.selectors()); - if (decl->getLevel() == SymbolTable::MODULE_LEVEL || - (env_->getIdentifier()->name() == SUPER_ && env_->getLevel() == decl->getLevel())) { + if (decl->getScope() == SymbolTable::MODULE_SCOPE || + (env_->getIdentifier()->name() == SUPER_ && env_->getScope() == decl->getScope())) { // global variable or local variable in leaf procedure return; } @@ -269,7 +269,7 @@ void LambdaLifter::visit(SetExpressionNode &node) { } void LambdaLifter::visit(TypeDeclarationNode &node) { - node.setLevel(level_); + node.setScope(scope_); } void LambdaLifter::visit(ArrayTypeNode &) {} diff --git a/src/analyzer/LambdaLifter.h b/src/analyzer/LambdaLifter.h index 94705f6..ca2d3fc 100644 --- a/src/analyzer/LambdaLifter.h +++ b/src/analyzer/LambdaLifter.h @@ -27,7 +27,7 @@ class LambdaLifter final : public Analysis, private NodeVisitor { ASTContext *context_; ModuleNode *module_; DeclarationNode *env_; - unsigned int level_; + unsigned int scope_; vector path_; static const string THIS_; @@ -86,7 +86,7 @@ class LambdaLifter final : public Analysis, private NodeVisitor { static bool envFieldResolver(QualifiedExpression *, const string &, TypeNode *); public: - explicit LambdaLifter(ASTContext *context) : context_(context), module_(), env_(), level_(), path_() { }; + explicit LambdaLifter(ASTContext *context) : context_(context), module_(), env_(), scope_(), path_() { }; ~LambdaLifter() override = default; void run(Logger &, Node *) override; diff --git a/src/codegen/llvm/LLVMIRBuilder.cpp b/src/codegen/llvm/LLVMIRBuilder.cpp index f596c09..82b081f 100644 --- a/src/codegen/llvm/LLVMIRBuilder.cpp +++ b/src/codegen/llvm/LLVMIRBuilder.cpp @@ -17,7 +17,7 @@ using std::vector; LLVMIRBuilder::LLVMIRBuilder(CompilerConfig &config, LLVMContext &builder, Module *module) : NodeVisitor(), config_(config), logger_(config_.logger()), builder_(builder), module_(module), value_(), values_(), types_(), leafTypes_(), hasArray_(), functions_(), strings_(), deref_ctx(), - level_(0), function_(), attrs_(AttrBuilder(builder)) { + scope_(0), function_(), attrs_(AttrBuilder(builder)) { attrs_ .addAttribute(Attribute::NoInline) .addAttribute(Attribute::NoUnwind) @@ -69,7 +69,7 @@ void LLVMIRBuilder::visit(ModuleNode &node) { function_ = ::cast(body.getCallee()); auto entry = BasicBlock::Create(builder_.getContext(), "entry", function_); builder_.SetInsertPoint(entry); - level_ = node.getLevel() + 1; + scope_ = node.getScope() + 1; // generate code to initialize imports for (auto &import : node.imports()) { import->accept(*this); @@ -128,7 +128,7 @@ void LLVMIRBuilder::visit(ProcedureNode &node) { arrayInitializers(var->getType()); values_[var] = value_; } - level_ = node.getLevel() + 1; + scope_ = node.getScope() + 1; node.statements()->accept(*this); if (node.getType()->getReturnType() == nullptr) { builder_.CreateRetVoid(); @@ -187,19 +187,25 @@ void LLVMIRBuilder::arrayInitializers(TypeNode *base, TypeNode *type, vectorisRecord()) { + // TODO initialize base types auto record_t = dynamic_cast(type); + // skip the first field (type descriptor tag) of a leaf record type + indices.push_back(builder_.getInt32(1)); + // check all record fields for initialization for (size_t i = 0; i < record_t->getFieldCount(); ++i) { indices.push_back(builder_.getInt32(i)); arrayInitializers(base, record_t->getField(i)->getType(), indices); indices.pop_back(); } + indices.pop_back(); } } void LLVMIRBuilder::visit(ImportNode &node) { std::string name = node.getModule()->name(); if (name == "SYSTEM") { - return; /* no initialization for pseudo modules */ + // no initialization for pseudo modules + return; } auto type = FunctionType::get(builder_.getInt32Ty(), {}); auto fun = module_->getOrInsertFunction(name, type); @@ -221,12 +227,12 @@ void LLVMIRBuilder::visit(QualifiedExpression &node) { // If the qualified expression refers to a type, no code has to be generated. return; } - auto level = decl->getLevel(); - if (level == 0 || level == 1) /* universe or global level */ { + auto scope = decl->getScope(); + if (scope == 0 || scope == 1) /* universe or global scope */ { value_ = values_[decl]; - } else if (level == level_) /* same procedure level */ { + } else if (scope == scope_) /* same procedure scope */ { value_ = values_[decl]; - } else if (level > level_) /* parent procedure level */ { + } else if (scope > scope_) /* parent procedure scope */ { logger_.error(node.pos(), "referencing variables of parent procedures is not yet supported."); } else /* error */ { logger_.error(node.pos(), "cannot reference variable of child procedure."); @@ -307,18 +313,17 @@ TypeNode *LLVMIRBuilder::selectors(TypeNode *base, SelectorIterator start, Selec // handle record field access auto field = dynamic_cast(sel)->getField(); auto record_t = dynamic_cast(selector_t); - auto recordTy = getLLVMType(record_t); - value = processGEP(base, value, indices); - // get address of first field - auto layout = module_->getDataLayout().getStructLayout((StructType *)recordTy); - auto offset = layout->getElementOffset(0); - for (size_t pos = 0; pos < record_t->getFieldCount(); pos++) { - if (field == record_t->getField(pos)) { - offset = layout->getElementOffset(pos) - offset; - value = builder_.CreateInBoundsGEP(builder_.getInt8Ty(), value, {builder_.getInt64(offset)}); - break; - } + // skip the first field (type descriptor tag) of a leaf record type + indices.push_back(builder_.getInt32(1)); + // navigate through the base records + unsigned current = field->getRecordType()->getLevel(); + for (unsigned level = current; level < record_t->getLevel(); level++) { + indices.push_back(builder_.getInt32(0)); } + // access the field by its index (increase index at levels > 0) + indices.push_back(builder_.getInt32(current == 0 ? field->getIndex() : field->getIndex() + 1)); + // output GEP up to the record field + value = processGEP(base, value, indices); selector_t = field->getType(); base = selector_t; } else if (sel->getNodeType() == NodeType::type) { @@ -1248,7 +1253,8 @@ LLVMIRBuilder::createNewCall(TypeNode *type, llvm::Value *param) { // TODO remove next line (bit-cast) once non-opaque pointers are no longer supported value_ = builder_.CreateBitCast(value_, getLLVMType(ptr)); Value *value = builder_.CreateStore(value_, param); - value_ = builder_.CreateLoad(builder_.getPtrTy(), param); + // TODO the following load is probably redundant as the address is already in `value_` + // value_ = builder_.CreateLoad(builder_.getPtrTy(), param); arrayInitializers(base); return value; } @@ -1604,7 +1610,7 @@ Type* LLVMIRBuilder::getLLVMType(TypeNode *type, bool leaf) { vector elemTys; auto recordTy = dynamic_cast(type); // add field for base record - if (recordTy->isExtened()) { + if (recordTy->isExtended()) { elemTys.push_back(getLLVMType(recordTy->getBaseType(), false)); } // add regular record fields @@ -1656,7 +1662,7 @@ Type* LLVMIRBuilder::getLLVMType(TypeNode *type, bool leaf) { auto leafType = leafTypes_[type]; if (!leafType) { leafType = StructType::create(builder_.getContext(), {builder_.getPtrTy(), result}); - leafType->setName(to_string(result->getStructName().data()) + ".leaf"); + leafType->setName("record." + to_string(*type->getIdentifier()) + ".leaf"); leafTypes_[type] = leafType; } result = leafType; diff --git a/src/codegen/llvm/LLVMIRBuilder.h b/src/codegen/llvm/LLVMIRBuilder.h index 719cfd2..cdb58f3 100644 --- a/src/codegen/llvm/LLVMIRBuilder.h +++ b/src/codegen/llvm/LLVMIRBuilder.h @@ -47,7 +47,7 @@ class LLVMIRBuilder final : private NodeVisitor { map functions_; map strings_; stack deref_ctx; - unsigned int level_; + unsigned int scope_; Function *function_; AttrBuilder attrs_; ASTContext *ast_; diff --git a/src/data/ast/DeclarationNode.cpp b/src/data/ast/DeclarationNode.cpp index 224c967..96914f5 100644 --- a/src/data/ast/DeclarationNode.cpp +++ b/src/data/ast/DeclarationNode.cpp @@ -35,16 +35,16 @@ void DeclarationNode::print(std::ostream &stream) const { stream << *getIdentifier() << ": " << *getType(); } -unsigned int DeclarationNode::index() const { - return index_; +unsigned int DeclarationNode::seqId() const { + return seqId_; } -void DeclarationNode::setLevel(unsigned int level) { - level_ = level; +void DeclarationNode::setScope(unsigned int scope) { + scope_ = scope; } -unsigned int DeclarationNode::getLevel() const { - return level_; +unsigned int DeclarationNode::getScope() const { + return scope_; } @@ -81,6 +81,21 @@ void VariableDeclarationNode::accept(NodeVisitor& visitor) { visitor.visit(*this); } +void FieldNode::setRecordType(RecordTypeNode *parent) { + parent_ = parent; +} + +void FieldNode::setIndex(unsigned index) { + index_ = index; +} + +RecordTypeNode *FieldNode::getRecordType() const { + return parent_; +} + +unsigned int FieldNode::getIndex() const { + return index_; +} void FieldNode::accept(NodeVisitor& visitor) { visitor.visit(*this); diff --git a/src/data/ast/DeclarationNode.h b/src/data/ast/DeclarationNode.h index abdd3c1..b5ecc80 100644 --- a/src/data/ast/DeclarationNode.h +++ b/src/data/ast/DeclarationNode.h @@ -27,12 +27,14 @@ class DeclarationNode : public Node { ModuleNode *module_; unique_ptr ident_; TypeNode *type_; - unsigned int index_; - unsigned int level_; + unsigned int seqId_; + unsigned int scope_; + + void setScope(unsigned int); public: - DeclarationNode(const NodeType nodeType, const FilePos &pos, unique_ptr ident, TypeNode *type, unsigned int index = 0) : - Node(nodeType, pos), module_(), ident_(std::move(ident)), type_(type), index_(index), level_() {}; + DeclarationNode(const NodeType nodeType, const FilePos &pos, unique_ptr ident, TypeNode *type, unsigned seqId = 0) : + Node(nodeType, pos), module_(), ident_(std::move(ident)), type_(type), seqId_(seqId), scope_() {}; ~DeclarationNode() override = default; void setModule(ModuleNode *); @@ -44,15 +46,19 @@ class DeclarationNode : public Node { void setType(TypeNode *); [[nodiscard]] TypeNode *getType() const; - [[nodiscard]] unsigned int index() const; + [[nodiscard]] unsigned int seqId() const; - void setLevel(unsigned int level); - [[nodiscard]] unsigned int getLevel() const; + [[nodiscard]] unsigned int getScope() const; void accept(NodeVisitor &) override = 0; void print(std::ostream &) const override; + friend class LambdaLifter; + friend class Sema; + friend class SymbolTable; + friend class SymbolImporter; + }; @@ -98,24 +104,37 @@ class TypeDeclarationNode final : public DeclarationNode { class VariableDeclarationNode final : public DeclarationNode { public: - VariableDeclarationNode(const FilePos &pos, unique_ptr ident, TypeNode *type, unsigned int index = 0) : - DeclarationNode(NodeType::variable, pos, std::move(ident), type, index) {}; + VariableDeclarationNode(const FilePos &pos, unique_ptr ident, TypeNode *type, unsigned seqId = 0) : + DeclarationNode(NodeType::variable, pos, std::move(ident), type, seqId) {}; ~VariableDeclarationNode() final = default; void accept(NodeVisitor& visitor) override; }; +class RecordTypeNode; class FieldNode final : public DeclarationNode { +private: + RecordTypeNode *parent_; + unsigned index_; + + void setRecordType(RecordTypeNode *); + void setIndex(unsigned); + public: - FieldNode(const FilePos &pos, unique_ptr ident, TypeNode *type, unsigned int index = 0) : - DeclarationNode(NodeType::field, pos, std::move(ident), type, index) {}; + FieldNode(const FilePos &pos, unique_ptr ident, TypeNode *type, unsigned seqId = 0) : + DeclarationNode(NodeType::field, pos, std::move(ident), type, seqId) {}; ~FieldNode() final = default; + [[nodiscard]] RecordTypeNode *getRecordType() const; + [[nodiscard]] unsigned getIndex() const; + void accept(NodeVisitor& visitor) override; + friend class RecordTypeNode; + }; @@ -125,8 +144,8 @@ class ParameterNode final : public DeclarationNode { bool var_; public: - ParameterNode(const FilePos &pos, unique_ptr ident, TypeNode *type, bool var, unsigned int index = 0) : - DeclarationNode(NodeType::parameter, pos, make_unique(ident->start(), ident->end(), ident->name()), type, index), var_(var) {}; + ParameterNode(const FilePos &pos, unique_ptr ident, TypeNode *type, bool var, unsigned seqId = 0) : + DeclarationNode(NodeType::parameter, pos, make_unique(ident->start(), ident->end(), ident->name()), type, seqId), var_(var) {}; ~ParameterNode() final = default; [[nodiscard]] bool isVar() const; diff --git a/src/data/ast/NodePrettyPrinter.cpp b/src/data/ast/NodePrettyPrinter.cpp index 970d7c9..9f5451d 100644 --- a/src/data/ast/NodePrettyPrinter.cpp +++ b/src/data/ast/NodePrettyPrinter.cpp @@ -85,7 +85,7 @@ void NodePrettyPrinter::qualident(DeclarationNode *decl) { void NodePrettyPrinter::visit(ModuleNode& node) { module_ = &node; indent(); - stream_ << "MODULE " << *node.getIdentifier() << "(*Scope:" << node.getLevel() << "*);" << std::endl; + stream_ << "MODULE " << *node.getIdentifier() << "(*Scope:" << node.getScope() << "*);" << std::endl; if (!node.imports().empty()) { stream_ << "IMPORT "; for (auto &import: node.imports()) { @@ -108,7 +108,7 @@ void NodePrettyPrinter::visit(ModuleNode& node) { void NodePrettyPrinter::visit(ProcedureNode& node) { indent(); - stream_ << "PROCEDURE " << *node.getIdentifier() << "(*Scope:" << node.getLevel() << "*)("; + stream_ << "PROCEDURE " << *node.getIdentifier() << "(*Scope:" << node.getScope() << "*)("; auto type = dynamic_cast(node.getType()); string sep; for (auto ¶m : type->parameters()) { @@ -202,7 +202,7 @@ void NodePrettyPrinter::selectors(std::vector> &selectors) } void NodePrettyPrinter::visit(ConstantDeclarationNode &node) { - stream_ << *node.getIdentifier() << "(*Scope:" << node.getLevel() << "*) = "; + stream_ << *node.getIdentifier() << "(*Scope:" << node.getScope() << "*) = "; node.getValue()->accept(*this); stream_ << "(*Type:" << *node.getType()->getIdentifier() << "*);" << std::endl; } @@ -214,19 +214,19 @@ void NodePrettyPrinter::visit(FieldNode &node) { void NodePrettyPrinter::visit(ParameterNode &node) { stream_ << (node.isVar() ? "VAR " : ""); - stream_ << *node.getIdentifier() << "(*Scope:" << node.getLevel() << "*): "; + stream_ << *node.getIdentifier() << "(*Scope:" << node.getScope() << "*): "; node.getType()->accept(*this); } void NodePrettyPrinter::visit(TypeDeclarationNode &node) { - stream_ << *node.getIdentifier() << "(*Scope:" << node.getLevel() << "*) = "; + stream_ << *node.getIdentifier() << "(*Scope:" << node.getScope() << "*) = "; isDecl_ = true; node.getType()->accept(*this); stream_ << "(*Size:" << node.getType()->getSize() << "*);" << std::endl; } void NodePrettyPrinter::visit(VariableDeclarationNode &node) { - stream_ << *node.getIdentifier() << "(*Scope:" << node.getLevel() << "*): "; + stream_ << *node.getIdentifier() << "(*Scope:" << node.getScope() << "*): "; node.getType()->accept(*this); stream_ << ';' << std::endl; } @@ -358,8 +358,8 @@ void NodePrettyPrinter::visit([[maybe_unused]] ProcedureTypeNode &node) { void NodePrettyPrinter::visit(RecordTypeNode &node) { if (node.isAnonymous() || isDecl_) { isDecl_ = false; - stream_ << "RECORD(*Level:" << node.level() << "*) "; - if (node.isExtened()) { + stream_ << "RECORD(*Level:" << node.getLevel() << "*) "; + if (node.isExtended()) { stream_ << "("; node.getBaseType()->accept(*this); stream_ << ") "; diff --git a/src/data/ast/RecordTypeNode.cpp b/src/data/ast/RecordTypeNode.cpp index 5f09e3c..8dacfa5 100644 --- a/src/data/ast/RecordTypeNode.cpp +++ b/src/data/ast/RecordTypeNode.cpp @@ -7,6 +7,16 @@ #include "RecordTypeNode.h" #include "NodeVisitor.h" +RecordTypeNode::RecordTypeNode(const FilePos &pos, RecordTypeNode *base, vector> fields) : + TypeNode(NodeType::record_type, pos, TypeKind::RECORD, 0), + fields_(std::move(fields)), base_(base), level_(base ? base->getLevel() + 1 : 0) { + unsigned index = 0; + for (auto& field : fields_) { + field->setRecordType(this); + field->setIndex(index++); + } +} + unsigned int RecordTypeNode::getSize() const { unsigned int size = 0; for (auto &&itr : fields_) { @@ -21,6 +31,9 @@ FieldNode *RecordTypeNode::getField(const std::string &name) const { return itr.get(); } } + if (base_) { + return base_->getField(name); + } return nullptr; } @@ -36,7 +49,7 @@ RecordTypeNode *RecordTypeNode::getBaseType() const { return base_; } -bool RecordTypeNode::isExtened() const { +bool RecordTypeNode::isExtended() const { return base_ != nullptr; } @@ -47,7 +60,7 @@ bool RecordTypeNode::extends(TypeNode *base) const { return this == base; } -unsigned short RecordTypeNode::level() const { +unsigned short RecordTypeNode::getLevel() const { return level_; } diff --git a/src/data/ast/RecordTypeNode.h b/src/data/ast/RecordTypeNode.h index 0bcee39..8a535bf 100644 --- a/src/data/ast/RecordTypeNode.h +++ b/src/data/ast/RecordTypeNode.h @@ -29,9 +29,7 @@ class RecordTypeNode final : public TypeNode { unsigned short level_; public: - RecordTypeNode(const FilePos &pos, RecordTypeNode *base, vector> fields) : - TypeNode(NodeType::record_type, pos, TypeKind::RECORD, 0), - fields_(std::move(fields)), base_(base), level_(base ? base->level() + 1 : 0) {}; + RecordTypeNode(const FilePos &pos, RecordTypeNode *base, vector> fields); ~RecordTypeNode() final = default; [[nodiscard]] unsigned int getSize() const final; @@ -41,10 +39,10 @@ class RecordTypeNode final : public TypeNode { [[nodiscard]] size_t getFieldCount(); [[nodiscard]] RecordTypeNode *getBaseType() const; - [[nodiscard]] bool isExtened() const; + [[nodiscard]] bool isExtended() const; [[nodiscard]] bool extends(TypeNode *) const override; - [[nodiscard]] unsigned short level() const; + [[nodiscard]] unsigned short getLevel() const; void accept(NodeVisitor &visitor) final; void print(ostream &out) const final; diff --git a/src/data/symtab/SymbolExporter.cpp b/src/data/symtab/SymbolExporter.cpp index 3d7f76f..c5b1474 100644 --- a/src/data/symtab/SymbolExporter.cpp +++ b/src/data/symtab/SymbolExporter.cpp @@ -217,7 +217,7 @@ void SymbolExporter::writeRecordType(SymbolFile *file, RecordTypeNode *type) { } // write out field type: if successive fields have the same type, // write it out the first time and write `NOTYPE` for following fields - writeType(file, field->index() == 0 ? field->getType() : nullptr); + writeType(file, field->seqId() == 0 ? field->getType() : nullptr); // write out field offset file->writeInt(static_cast(offset)); offset += field->getType()->getSize(); @@ -237,5 +237,5 @@ void SymbolExporter::writeParameter(SymbolFile *file, ParameterNode *param) { } // write out parameter type: if successive parameters have the same type, // only write it out the first time and write `NOTYPE` for following parameters - writeType(file, param->index() == 0 ? param->getType() : nullptr); + writeType(file, param->seqId() == 0 ? param->getType() : nullptr); } \ No newline at end of file diff --git a/src/data/symtab/SymbolImporter.cpp b/src/data/symtab/SymbolImporter.cpp index b19efa7..f6b2e84 100644 --- a/src/data/symtab/SymbolImporter.cpp +++ b/src/data/symtab/SymbolImporter.cpp @@ -261,7 +261,7 @@ TypeNode *SymbolImporter::readProcedureType(SymbolFile *file) { index++; } auto param = make_unique(EMPTY_POS, make_unique("_"), type, (var == 0), index); - param->setLevel(SymbolTable::MODULE_LEVEL); + param->setScope(SymbolTable::MODULE_SCOPE); params.push_back(std::move(param)); // check for terminator ch = file->readChar(); diff --git a/src/data/symtab/SymbolTable.cpp b/src/data/symtab/SymbolTable.cpp index 3b1f95a..055e4ed 100644 --- a/src/data/symtab/SymbolTable.cpp +++ b/src/data/symtab/SymbolTable.cpp @@ -13,11 +13,11 @@ using std::make_unique; using std::string; -const unsigned int SymbolTable::GLOBAL_LEVEL = 0; -const unsigned int SymbolTable::MODULE_LEVEL = 1; +const unsigned int SymbolTable::GLOBAL_SCOPE = 0; +const unsigned int SymbolTable::MODULE_SCOPE = 1; SymbolTable::SymbolTable() : scopes_(), aliases_(), scope_(), references_() { - universe_ = make_unique(GLOBAL_LEVEL, nullptr); + universe_ = make_unique(GLOBAL_SCOPE, nullptr); } SymbolTable::~SymbolTable() = default; @@ -25,7 +25,7 @@ SymbolTable::~SymbolTable() = default; void SymbolTable::import(const string &module, const string &name, DeclarationNode *node) { auto scope = getModule(module); if (scope) { - node->setLevel(MODULE_LEVEL); + node->setScope(MODULE_SCOPE); scope->insert(name, node); } else { // TODO throw exception @@ -122,7 +122,7 @@ void SymbolTable::addModule(const string &module, bool activate) { std::cerr << "Illegal symbol table state: namespace " + module + " already exists." << std::endl; exit(1); } - auto scope = make_unique(GLOBAL_LEVEL, nullptr); + auto scope = make_unique(GLOBAL_SCOPE, nullptr); if (activate) { scope_ = scope.get(); } @@ -145,7 +145,7 @@ void SymbolTable::openScope() { } void SymbolTable::closeScope() { - if (scope_->getLevel() > GLOBAL_LEVEL) { + if (scope_->getLevel() > GLOBAL_SCOPE) { scope_ = scope_->getParent(); } else { // TODO throw exception @@ -156,7 +156,7 @@ void SymbolTable::closeScope() { unsigned int SymbolTable::getLevel() const { if (scope_ == nullptr) { - return GLOBAL_LEVEL; + return GLOBAL_SCOPE; } else { return scope_->getLevel(); } diff --git a/src/data/symtab/SymbolTable.h b/src/data/symtab/SymbolTable.h index a3c24dd..0009528 100644 --- a/src/data/symtab/SymbolTable.h +++ b/src/data/symtab/SymbolTable.h @@ -70,8 +70,8 @@ class SymbolTable { [[nodiscard]] unsigned int getLevel() const; - static const unsigned int GLOBAL_LEVEL; - static const unsigned int MODULE_LEVEL; + static const unsigned int GLOBAL_SCOPE; + static const unsigned int MODULE_SCOPE; }; diff --git a/src/sema/Sema.cpp b/src/sema/Sema.cpp index 9452793..2beb81c 100644 --- a/src/sema/Sema.cpp +++ b/src/sema/Sema.cpp @@ -71,7 +71,7 @@ unique_ptr Sema::onModuleStart(const FilePos &start, unique_ptr ident) { auto module = make_unique(start, std::move(ident)); assertUnique(module->getIdentifier(), module.get()); - module->setLevel(symbols_->getLevel()); + module->setScope(symbols_->getLevel()); onBlockStart(); return module; } @@ -128,7 +128,7 @@ Sema::onConstant(const FilePos &start, [[maybe_unused]] const FilePos &end, } auto node = make_unique(start, std::move(ident), std::move(expr), expr ? expr->getType() : nullTy_); assertUnique(node->getIdentifier(), node.get()); - node->setLevel(symbols_->getLevel()); + node->setScope(symbols_->getLevel()); node->setModule(context_->getTranslationUnit()); checkExport(node.get()); return node; @@ -139,7 +139,7 @@ Sema::onType(const FilePos &start, [[maybe_unused]] const FilePos &end, unique_ptr ident, TypeNode *type) { auto node = make_unique(start, std::move(ident), type ? type : nullTy_); assertUnique(node->getIdentifier(), node.get()); - node->setLevel(symbols_->getLevel()); + node->setScope(symbols_->getLevel()); node->setModule(context_->getTranslationUnit()); checkExport(node.get()); if (type) { @@ -248,7 +248,7 @@ Sema::onParameter(const FilePos &start, [[maybe_unused]] const FilePos &end, } auto node = make_unique(start, std::move(ident), type, is_var, index); assertUnique(node->getIdentifier(), node.get()); - node->setLevel(symbols_->getLevel()); + node->setScope(symbols_->getLevel()); return node; } @@ -274,7 +274,7 @@ Sema::onRecordType(const FilePos &start, const FilePos &end, for (size_t i = 0; i < node->getFieldCount(); i++) { auto field = node->getField(i); auto name = field->getIdentifier()->name(); - if (base && resolveRecordField(base, name)) { + if (base && base->getField(name)) { logger_.error(field->pos(), "redefinition of record field: " + to_string(*field->getIdentifier()) + "."); continue; } @@ -337,7 +337,7 @@ Sema::onVariable(const FilePos &start, [[maybe_unused]] const FilePos &end, } auto node = make_unique(start, std::move(ident), type, index); assertUnique(node->getIdentifier(), node.get()); - node->setLevel(symbols_->getLevel()); + node->setScope(symbols_->getLevel()); node->setModule(context_->getTranslationUnit()); checkExport(node.get()); return node; @@ -348,7 +348,7 @@ Sema::onProcedureStart(const FilePos &start, unique_ptr ident) { procs_.push(make_unique(start, std::move(ident))); auto proc = procs_.top().get(); assertUnique(proc->getIdentifier(), proc); - proc->setLevel(symbols_->getLevel()); + proc->setScope(symbols_->getLevel()); proc->setModule(context_->getTranslationUnit()); checkExport(proc); onBlockStart(); @@ -366,7 +366,7 @@ Sema::onProcedureEnd([[maybe_unused]] const FilePos &end, unique_ptr iden logger_.error(proc->pos(), "result type of a procedure can neither be a record nor an array."); } if (proc->isExtern()) { - if (proc->getLevel() != SymbolTable::MODULE_LEVEL) { + if (proc->getScope() != SymbolTable::MODULE_SCOPE) { logger_.error(proc->pos(), "only top-level procedures can be external."); } } else { @@ -878,7 +878,7 @@ FieldNode *Sema::onRecordField(TypeNode *base, RecordField *sel) { } auto record = dynamic_cast(base); auto ref = dynamic_cast(sel); - auto field = resolveRecordField(record, ref->ident()->name()); + auto field = record->getField(ref->ident()->name()); if (!field) { logger_.error(ref->pos(), "undefined record field: " + to_string(*ref->ident()) + "."); return nullptr; @@ -1604,7 +1604,7 @@ Sema::assertUnique(IdentDef *ident, DeclarationNode *node) { void Sema::checkExport(DeclarationNode *node) { if (node->getIdentifier()->isExported()) { - if (node->getLevel() != SymbolTable::MODULE_LEVEL) { + if (node->getScope() != SymbolTable::MODULE_SCOPE) { logger_.error(node->getIdentifier()->start(), "only top-level declarations can be exported."); } } else { @@ -1836,12 +1836,3 @@ Sema::intType(int64_t value) { } return integerTy_; } - -FieldNode * -Sema::resolveRecordField(RecordTypeNode *type, const std::string &name) { - auto field = type->getField(name); - if (!field && type->isExtened()) { - return resolveRecordField(type->getBaseType(), name); - } - return field; -} diff --git a/src/sema/Sema.h b/src/sema/Sema.h index 8675302..f8f4671 100644 --- a/src/sema/Sema.h +++ b/src/sema/Sema.h @@ -60,8 +60,6 @@ class Sema { bool assertCompatible(const FilePos &, TypeNode *, TypeNode *, bool = false, bool = false); TypeNode *commonType(const FilePos &, TypeNode *, TypeNode *) const; - static FieldNode *resolveRecordField(RecordTypeNode *, const string&); - static string format(const TypeNode *, bool = false); static int64_t euclidean_mod(int64_t, int64_t); diff --git a/src/system/OberonSystem.cpp b/src/system/OberonSystem.cpp index 1af7a4b..5d682d8 100644 --- a/src/system/OberonSystem.cpp +++ b/src/system/OberonSystem.cpp @@ -88,7 +88,7 @@ OberonSystem::createProcedure(ProcKind kind, const string& name, const vectorgetLevel() == SymbolTable::GLOBAL_LEVEL) { + if (symbols_->getLevel() == SymbolTable::GLOBAL_SCOPE) { symbols_->insertGlobal(ptr->getIdentifier()->name(), ptr); } else { symbols_->import(module_, ptr->getIdentifier()->name(), ptr); diff --git a/test/oberon/RecordExt.Mod b/test/oberon/RecordExt.Mod index 7880187..5a347fa 100644 --- a/test/oberon/RecordExt.Mod +++ b/test/oberon/RecordExt.Mod @@ -11,11 +11,11 @@ TYPE IntNode = POINTER TO IntNodeDesc; IntNodeDesc = RECORD (NodeDesc) val: INTEGER - END; + END;*) Point2D = RECORD (ObjectDesc) x, y: INTEGER END; - Point3D = RECORD (Point2D) + (* Point3D = RECORD (Point2D) z: INTEGER END;*) Shape = POINTER TO ShapeDesc; @@ -24,8 +24,7 @@ TYPE END; Circle = POINTER TO CircleDesc; CircleDesc* = RECORD (ShapeDesc) - (*centre: Point2D;*) - x, y: INTEGER; + centre: Point2D; radius*: REAL END; (*Sphere = RECORD (ObjectDesc) @@ -47,19 +46,34 @@ BEGIN (*c(Sphere).radius := r;*) END Init; +PROCEDURE PrintPoint2D(p: Point2D); +BEGIN + Out.String("[x: "); Out.Int(p.x, 0); Out.String(", y: "); Out.Int(p.y, 0); Out.Char("]") +END PrintPoint2D; + +PROCEDURE PrintCircle(c: CircleDesc); +BEGIN + Out.String("[centre: "); PrintPoint2D(c.centre); + Out.String(", radius: "); Out.Real(c.radius, 0); + Out.String(", area: "); Out.Real(c.area, 0); Out.Char("]") +END PrintCircle; + BEGIN (*nd.val := 0; Init(c, 3.141);*) + cd.centre.x := 1; + cd.centre.y := 1; + cd.radius := 10.0; + cd.area := 3.14159265359 * cd.radius * cd.radius; + PrintCircle(cd); Out.Ln; + NEW(c); - c.x := -1; - c.y := -1; + c.centre.x := -1; + c.centre.y := -1; c.radius := 1.0; c.area := 3.14159265359 * c.radius * c.radius; - Out.Int(c.x, 0); Out.Ln; - Out.Int(c.y, 0); Out.Ln; - Out.Real(c.radius, 0); Out.Ln; - Out.Real(c.area, 0); Out.Ln; - (*FREE(c);*) + PrintCircle(c^); Out.Ln; + FREE(c); (*NEW(inode); node := inode;*) (*inode := node;*)