diff --git a/.run/spice build.run.xml b/.run/spice build.run.xml index 018d2e961..e53863c38 100644 --- a/.run/spice build.run.xml +++ b/.run/spice build.run.xml @@ -1,5 +1,5 @@ - + diff --git a/docs/docs/language/builtins.md b/docs/docs/language/builtins.md index 0cdc00d17..3cd37ed6d 100644 --- a/docs/docs/language/builtins.md +++ b/docs/docs/language/builtins.md @@ -8,7 +8,7 @@ Spice offers five builtin functions out of the box. Those can be used anywhere w Printf works the same as the `printf` function in C and is designed for printing a string to the standard text output (cout). ### Signature -`void printf(string template, ...args)` +`int printf(string template, ...args)` `template`: Template string, which can contain placeholders for values, passed as args to the `printf` builtin.
`args`: Arbitrary number of arguments of any type. The particular type and the order of the types have to match the placeholders of the template string. diff --git a/media/test-project/test.spice b/media/test-project/test.spice index 12351122d..17dd462ca 100644 --- a/media/test-project/test.spice +++ b/media/test-project/test.spice @@ -1,5 +1,27 @@ -#[core.compiler.mangle = false] -public f fibo(int n) { - if n <= 1 { return n; } - return fibo(n - 1) + fibo(n - 2); +type ITest interface { + p test(); } + +type InnerTest struct : ITest { + int a +} + +p InnerTest.ctor() { + this.a = 0; +} + +p InnerTest.test() { + printf("InnerTest.test()\n"); +} + +type Test struct { + ITest* test +} + +f main() { + InnerTest innerTest; + Test test = Test{&innerTest}; + ITest* test2 = &innerTest; + test2.test(); + test.test.test(); +} \ No newline at end of file diff --git a/src-bootstrap/compiler-pass.spice b/src-bootstrap/compiler-pass.spice index 1da742212..73b977fd0 100644 --- a/src-bootstrap/compiler-pass.spice +++ b/src-bootstrap/compiler-pass.spice @@ -17,8 +17,7 @@ p CompilerPass.ctor(IGlobalResourceManager& resourceManager, ISourceFile* source this.resourceManager = resourceManager; this.cliOptions = resourceManager.cliOptions; this.sourceFile = sourceFile; - this.rootScope = sourceFile != nil ? sourceFile.globalScope : nil; - this.currentScope = sNonOwned(this.rootScope); + this.currentScope = this.rootScope = sourceFile != nil ? sourceFile.globalScope : nil; } /** diff --git a/src/global/RuntimeModuleManager.h b/src/global/RuntimeModuleManager.h index 3f5bc0db6..1dee9b21f 100644 --- a/src/global/RuntimeModuleManager.h +++ b/src/global/RuntimeModuleManager.h @@ -44,7 +44,7 @@ const std::unordered_map FCT_NAME_TO_RT_MODULE_MAPP {"sNew", MEMORY_RT}, {"sPlacementNew", MEMORY_RT}, {"sDelete", MEMORY_RT}, - {"sNonOwned", MEMORY_RT}, + {"sCompare", MEMORY_RT}, // Result RT {"ok", RESULT_RT}, {"err", RESULT_RT}, diff --git a/src/typechecker/TypeChecker.cpp b/src/typechecker/TypeChecker.cpp index ed268ea0b..7ef0b6b1e 100644 --- a/src/typechecker/TypeChecker.cpp +++ b/src/typechecker/TypeChecker.cpp @@ -131,7 +131,7 @@ std::any TypeChecker::visitForLoop(ForLoopNode *node) { visit(node->initDecl()); // Visit condition - QualType conditionType = std::any_cast(visit(node->condAssign())).type; + const QualType conditionType = std::any_cast(visit(node->condAssign())).type; HANDLE_UNRESOLVED_TYPE_PTR(conditionType) // Check if condition evaluates to bool if (!conditionType.is(TY_BOOL)) @@ -281,7 +281,7 @@ std::any TypeChecker::visitDoWhileLoop(DoWhileLoopNode *node) { visit(node->body()); // Visit condition - QualType conditionType = std::any_cast(visit(node->condition())).type; + const QualType conditionType = std::any_cast(visit(node->condition())).type; HANDLE_UNRESOLVED_TYPE_PTR(conditionType) // Check if condition evaluates to bool if (!conditionType.is(TY_BOOL)) @@ -296,7 +296,7 @@ std::any TypeChecker::visitIfStmt(IfStmtNode *node) { // Visit condition AssignExprNode *condition = node->condition(); - QualType conditionType = std::any_cast(visit(condition)).type; + const QualType conditionType = std::any_cast(visit(condition)).type; HANDLE_UNRESOLVED_TYPE_PTR(conditionType) // Check if condition evaluates to bool if (!conditionType.is(TY_BOOL)) @@ -339,7 +339,7 @@ std::any TypeChecker::visitElseStmt(ElseStmtNode *node) { std::any TypeChecker::visitSwitchStmt(SwitchStmtNode *node) { // Check expression type AssignExprNode *expr = node->assignExpr(); - QualType exprType = std::any_cast(visit(expr)).type; + const QualType exprType = std::any_cast(visit(expr)).type; HANDLE_UNRESOLVED_TYPE_PTR(exprType) if (!exprType.isOneOf({TY_INT, TY_SHORT, TY_LONG, TY_BYTE, TY_CHAR, TY_BOOL})) SOFT_ERROR_ER(node->assignExpr(), SWITCH_EXPR_MUST_BE_PRIMITIVE, @@ -349,7 +349,7 @@ std::any TypeChecker::visitSwitchStmt(SwitchStmtNode *node) { visitChildren(node); // Check if case constant types match switch expression type - for (CaseBranchNode *caseBranchNode : node->caseBranches()) + for (const CaseBranchNode *caseBranchNode : node->caseBranches()) for (CaseConstantNode *constantNode : caseBranchNode->caseConstants()) { const QualType constantType = std::any_cast(visit(constantNode)).type; if (!constantType.matches(exprType, false, true, true)) @@ -807,7 +807,7 @@ std::any TypeChecker::visitPrintfCall(PrintfCallNode *node) { if (placeholderCount < node->args().size()) SOFT_ERROR_ER(node, PRINTF_ARG_COUNT_ERROR, "The placeholder string contains less placeholders than arguments") - return ExprResult{node->setEvaluatedSymbolType(QualType(TY_BOOL), manIdx)}; + return ExprResult{node->setEvaluatedSymbolType(QualType(TY_INT), manIdx)}; } std::any TypeChecker::visitSizeofCall(SizeofCallNode *node) { @@ -962,7 +962,7 @@ std::any TypeChecker::visitTernaryExpr(TernaryExprNode *node) { // Visit condition LogicalOrExprNode *condition = node->operands()[0]; - QualType conditionType = std::any_cast(visit(condition)).type; + const QualType conditionType = std::any_cast(visit(condition)).type; HANDLE_UNRESOLVED_TYPE_ER(conditionType) QualType trueType; @@ -1793,8 +1793,8 @@ std::any TypeChecker::visitFctCall(FctCallNode *node) { // Set return type to 'this' type returnType = data.thisType; } else if (data.callee->isProcedure()) { - // Procedures always have the return type 'bool' - returnType = QualType(TY_BOOL); + // Procedures always have the return type 'dyn' + returnType = QualType(TY_DYN); } else { returnType = data.callee->returnType; } diff --git a/std/data/deque.spice b/std/data/deque.spice new file mode 100644 index 000000000..978eb8c47 --- /dev/null +++ b/std/data/deque.spice @@ -0,0 +1,258 @@ +// Constants +const unsigned long INITIAL_ALLOC_COUNT = 5l; +const unsigned int RESIZE_FACTOR = 2; + +// Add generic type definition +type T dyn; + +/** + * A deque in Spice is a commonly used data structure that allows insertion and removal + * of elements from both ends: front and back. + * + * Time complexity: + * Insert at front: O(1) + * Insert at back: O(1) + * Delete from front: O(1) + * Delete from back: O(1) + * Search: O(n) + * + * Deques pre-allocate space using an initial size and a resize factor to not have to re-allocate + * with every item pushed. This implementation uses a ring buffer, that wraps around when one of the + * indices reach the start or end of the allocated heap space. + */ +public type Deque struct { + heap T* contents // Pointer to the first data element + unsigned long capacity // Allocated number of items + unsigned long size = 0l // Current number of items + long idxFront = 0l // Index for front access + long idxBack = 0l // Index for back access +} + +public p Deque.ctor(unsigned long initAllocItems, const T& defaultValue) { + // Allocate space for the initial number of elements + this.ctor(initAllocItems); + // Fill in the default values + for unsigned long index = 0l; index < initAllocItems; index++ { + unsafe { + this.contents[index] = defaultValue; + } + } + this.size = initAllocItems; +} + +public p Deque.ctor(unsigned int initAllocItems) { + this.ctor((long) initAllocItems); +} + +public p Deque.ctor(unsigned long initAllocItems = INITIAL_ALLOC_COUNT) { + // Allocate space for the initial number of elements + const long itemSize = sizeof(type T) / 8l; + unsafe { + Result allocResult = sAlloc(itemSize * initAllocItems); + this.contents = (heap T*) allocResult.unwrap(); + } + this.capacity = initAllocItems; +} + +/** + * Add an item at the back of the deque + */ +public p Deque.pushBack(const T& item) { + // Check if we need to re-allocate memory + if this.isFull() { + this.resize(this.capacity * RESIZE_FACTOR); + } + + // Insert the element at the back + unsafe { + this.contents[this.idxBack] = item; + } + this.idxBack++; + this.idxBack %= this.capacity; + + // Increase size + this.size++; +} + +/** + * Add an item at the front of the deque + */ +public p Deque.pushFront(const T& item) { + // Check if we need to re-allocate memory + if this.isFull() { + this.resize(this.capacity * RESIZE_FACTOR); + } + + // Insert the element at the front + this.idxFront = this.idxFront + this.capacity - 1; + this.idxFront %= this.capacity; + unsafe { + this.contents[this.idxFront] = item; + } + + // Increase size + this.size++; +} + +/** + * Retrieve the first item and remove it + * + * @return First item + */ +public f Deque.popFront() { + if this.isEmpty() { panic(Error("Deque is empty")); } + + // Get item value + unsafe { + result = this.contents[this.idxFront]; + } + + // Adjust indices and size + this.idxFront++; + this.idxFront %= this.capacity; + this.size--; +} + +/** + * Retrieve the last item and remove it + * + * @return Last item + */ +public f Deque.popBack() { + if this.isEmpty() { panic(Error("Deque is empty")); } + + // Get item value + unsafe { + result = this.contents[this.idxBack]; + } + + // Adjust indices and size + this.idxBack = this.idxBack - 1 + this.capacity; + this.idxBack %= this.capacity; + this.size--; +} + +/** + * Retrieve the first item without removing it from the deque + * + * @return First item + */ +public f Deque.front() { + if this.isEmpty() { panic(Error("Access index out of bounds")); } + unsafe { + return this.contents[this.idxFront]; + } +} + +/** + * Retrieve the last item without removing it from the deque + * + * @return Last item + */ +public f Deque.back() { + if this.isEmpty() { panic(Error("Access index out of bounds")); } + unsafe { + return this.contents[this.idxBack - 1]; + } +} + +/** + * Retrieve the current size of the deque + * + * @return Current size of the deque + */ +public f Deque.getSize() { + return this.size; +} + +/** + * Retrieve the current capacity of the deque + * + * @return Current capacity of the deque + */ +public f Deque.getCapacity() { + return this.capacity; +} + +/** + * Checks if the deque contains any items at the moment + * + * @return Empty or not empty + */ +public f Deque.isEmpty() { + return this.size == 0; +} + +/** + * Checks if the deque exhausts its capacity and needs to resize at the next call of push + * + * @return Full or not full + */ +public f Deque.isFull() { + return this.size == this.capacity; +} + +/** + * Reserves `itemCount` items + */ +public p Deque.reserve(unsigned long itemCount) { + if itemCount > this.capacity { + this.resize(itemCount); + } +} + +/** + * Frees allocated memory that is not used by the deque + */ +public p Deque.pack() { + // Return if no packing is required + if this.isFull() { return; } + // Pack the array + this.resize(this.size); +} + +public f operator==(const Deque& lhs, const Deque& rhs) { + // Compare the sizes + if lhs.size != rhs.size { return false; } + // Compare the contents + unsafe { + for unsigned long index = 0l; index < lhs.size; index++ { + const unsigned long lhsIdx = (lhs.idxFront + index) % lhs.capacity; + const unsigned long rhsIdx = (rhs.idxFront + index) % rhs.capacity; + if lhs.contents[lhsIdx] != rhs.contents[rhsIdx] { + return false; + } + } + } + return true; +} + +public f operator!=(const Deque& lhs, const Deque& rhs) { + return !(lhs == rhs); +} + +/** + * Re-allocates heap space for the deque contents + */ +p Deque.resize(unsigned long itemCount) { + // Allocate the new memory + const unsigned long itemSize = sizeof(type T) / 8l; + unsafe { + // Allocate a new chunk of memory with the requested size + unsigned long newSize = itemSize * itemCount; + Result allocResult = sAlloc(newSize); + // Take ownership of the old pointer to free it at the end of the scope + heap T* oldAddress = this.contents; + this.contents = (heap T*) allocResult.unwrap(); + // Copy data over to new memory chunk + for unsigned long newIndex = 0l; newIndex < this.size; newIndex++ { + unsigned long oldIndex = this.idxFront + newIndex; + oldIndex %= this.capacity; + this.contents[newIndex] = oldAddress[oldIndex]; + } + } + // Set new capacity + this.capacity = itemCount; + this.idxFront = 0l; + this.idxBack = this.size; +} diff --git a/std/data/queue.spice b/std/data/queue.spice index f6790474c..edf7cbe45 100644 --- a/std/data/queue.spice +++ b/std/data/queue.spice @@ -17,18 +17,18 @@ type T dyn; * with every item pushed. */ public type Queue struct { - heap T* contents // Pointer to the first data element - unsigned long capacity // Allocated number of items - unsigned long size // Current number of items - unsigned long idxFront // Index for front access - unsigned long idxBack // Index for back access + heap T* contents // Pointer to the first data element + unsigned long capacity // Allocated number of items + unsigned long size = 0l // Current number of items + unsigned long idxFront = 0l // Index for front access + unsigned long idxBack = 0l // Index for back access } public p Queue.ctor(unsigned long initAllocItems, const T& defaultValue) { // Allocate space for the initial number of elements this.ctor(initAllocItems); // Fill in the default values - for int index = 0; index < initAllocItems; index++ { + for unsigned long index = 0l; index < initAllocItems; index++ { unsafe { this.contents[index] = defaultValue; } @@ -48,10 +48,7 @@ public p Queue.ctor(unsigned long initAllocItems = INITIAL_ALLOC_COUNT) { Result allocResult = sAlloc(itemSize * initAllocItems); this.contents = (heap T*) allocResult.unwrap(); } - this.size = 0l; this.capacity = initAllocItems; - this.idxFront = 0l; - this.idxBack = 0l; } /** @@ -65,7 +62,7 @@ public p Queue.push(const T& item) { // Insert the element at the back unsafe { - this.contents[(int) this.idxBack++] = item; + this.contents[this.idxBack++] = item; } // Increase size @@ -80,7 +77,7 @@ public p Queue.push(const T& item) { public f Queue.pop() { this.size--; unsafe { - return this.contents[(int) this.idxFront++]; + return this.contents[this.idxFront++]; } } @@ -92,7 +89,7 @@ public f Queue.pop() { public f Queue.front() { if this.isEmpty() { panic(Error("Access index out of bounds")); } unsafe { - return this.contents[(int) this.idxFront]; + return this.contents[this.idxFront]; } } @@ -104,7 +101,7 @@ public f Queue.front() { public f Queue.back() { if this.isEmpty() { panic(Error("Access index out of bounds")); } unsafe { - return this.contents[(int) this.idxBack]; + return this.contents[this.idxBack]; } } @@ -169,7 +166,11 @@ public f operator==(const Queue& lhs, const Queue& rhs) { // Compare the contents unsafe { for unsigned long index = 0l; index < lhs.size; index++ { - if lhs.contents[index] != rhs.contents[index] { return false; } + const unsigned long lhsIdx = (lhs.idxFront + index) % lhs.capacity; + const unsigned long rhsIdx = (rhs.idxFront + index) % rhs.capacity; + if lhs.contents[lhsIdx] != rhs.contents[rhsIdx] { + return false; + } } } return true; @@ -184,11 +185,11 @@ public f operator!=(const Queue& lhs, const Queue& rhs) { */ p Queue.resize(unsigned long itemCount) { // Allocate the new memory - const long itemSize = sizeof(type T) / 8l; + const unsigned long itemSize = sizeof(type T) / 8l; unsafe { + // Allocate a new chunk of memory with the requested size heap byte*& oldAddress = (heap byte*) this.contents; - unsigned long newSize = (unsigned long) (itemSize * itemCount); - Result allocResult = sRealloc(oldAddress, newSize); + Result allocResult = sRealloc(oldAddress, itemSize * itemCount); this.contents = (heap T*) allocResult.unwrap(); } // Set new capacity diff --git a/std/data/stack.spice b/std/data/stack.spice index 17dcebe4c..92b03295b 100644 --- a/std/data/stack.spice +++ b/std/data/stack.spice @@ -19,7 +19,7 @@ type T dyn; public type Stack struct { heap T* contents // Pointer to the first data element unsigned long capacity // Allocated number of items - unsigned long size // Current number of items + unsigned long size = 0l // Current number of items } public p Stack.ctor(unsigned long initAllocItems, const T &defaultValue) { @@ -45,7 +45,6 @@ public p Stack.ctor(unsigned long initAllocItems = INITIAL_ALLOC_COUNT) { Result allocResult = sAlloc(itemSize * initAllocItems); this.contents = (heap T*) allocResult.unwrap(); } - this.size = 0l; this.capacity = initAllocItems; } @@ -60,7 +59,7 @@ public p Stack.push(const T& item) { // Insert the element at the back unsafe { - this.contents[(int) this.size++] = item; + this.contents[this.size++] = item; } } @@ -71,7 +70,7 @@ public f Stack.pop() { if this.isEmpty() { panic(Error("The stack is empty")); } // Pop the element from the stack unsafe { - return this.contents[(int) --this.size]; + return this.contents[--this.size]; } } @@ -82,7 +81,7 @@ public f Stack.top() { if this.isEmpty() { panic(Error("The stack is empty")); } // Peek the element from the stack unsafe { - return this.contents[(int) this.size - 1l]; + return this.contents[this.size - 1l]; } } @@ -136,12 +135,8 @@ public f operator==(const Stack& lhs, const Stack& rhs) { // Compare the sizes if lhs.size != rhs.size { return false; } // Compare the contents - unsafe { - for unsigned long i = 0l; index < lhs.size; i++ { - if lhs.contents[i] != rhs.contents[i] { return false; } - } - } - return true; + const unsigned long itemSize = sizeof(type T) / 8l; + return sCompare(lhs.contents, rhs.contents, itemSize * lhs.size); } public f operator!=(const Stack& lhs, const Stack& rhs) { diff --git a/std/data/vector.spice b/std/data/vector.spice index f7b0c6803..10b357cdb 100644 --- a/std/data/vector.spice +++ b/std/data/vector.spice @@ -225,12 +225,8 @@ public f operator==(const Vector& lhs, const Vector& rhs) { // Compare the sizes if lhs.size != rhs.size { return false; } // Compare the contents - unsafe { - for unsigned long index = 0l; index < lhs.size; index++ { - if lhs.contents[index] != rhs.contents[index] { return false; } - } - } - return true; + const unsigned long itemSize = sizeof(type T) / 8l; + return sCompare(lhs.contents, rhs.contents, itemSize * lhs.size); } public f operator!=(const Vector& lhs, const Vector& rhs) { diff --git a/std/runtime/memory_rt.spice b/std/runtime/memory_rt.spice index ed33fc18e..54e106dca 100644 --- a/std/runtime/memory_rt.spice +++ b/std/runtime/memory_rt.spice @@ -5,6 +5,7 @@ ext f malloc(unsigned long); ext f realloc(heap byte*, unsigned long); ext p free(heap byte*); ext p memcpy(heap byte*, heap byte*, unsigned long); +ext f memcmp(const heap byte*, const heap byte*, unsigned long); // Generic type defs type T dyn; @@ -35,6 +36,7 @@ public f sAllocUnsafe(unsigned long size) { /** * Reallocates a block of memory to the given size. + * Subsequently moves the data from the old memory space to the new one. * * @param ptr The pointer to the block to reallocate. * @param size The new size of the block. @@ -134,25 +136,14 @@ public p sDelete(heap T*& ptr) { } /** - * Creates a owned pointer to the given non-owned, heap-allocated instance. + * Compares the memory behind pointer A and pointer B for equality. * - * @param ptr The pointer to the non-owned, heap-allocated instance - * @return An owned pointer to the heap-allocated instance + * @param a Pointer A + * @param b Pointer B + * @return Is memory equal */ -public f sOwned(T* ptr) { +public f sCompare(const heap T* a, const heap T* b, unsigned long size) { unsafe { - return (heap T*) ptr; - } -} - -/** - * Creates a non-owned pointer to the given owned, heap-allocated instance. - * - * @param ptr The pointer to the owned, heap-allocated instance - * @return A non-owned pointer to the heap-allocated instance - */ -public f sNonOwned(heap T* ptr) { - unsafe { - return (T*) ptr; + return memcmp((const byte*) a, (const byte*) b, size) == 0; } } diff --git a/test/test-files/benchmark/success-hello-world/type-registry.out b/test/test-files/benchmark/success-hello-world/type-registry.out index 88cb97e41..e789cbfb4 100644 --- a/test/test-files/benchmark/success-hello-world/type-registry.out +++ b/test/test-files/benchmark/success-hello-world/type-registry.out @@ -1,4 +1,3 @@ -bool f() f() int diff --git a/test/test-files/irgenerator/debug-info/success-dbg-info-complex/ir-code.ll b/test/test-files/irgenerator/debug-info/success-dbg-info-complex/ir-code.ll index f74c323f2..a8d1c5487 100644 --- a/test/test-files/irgenerator/debug-info/success-dbg-info-complex/ir-code.ll +++ b/test/test-files/irgenerator/debug-info/success-dbg-info-complex/ir-code.ll @@ -534,11 +534,11 @@ attributes #2 = { cold noreturn nounwind } !42 = !{!"branch_weights", i32 2000, i32 1} !43 = !DILocation(line: 13, column: 14, scope: !15) !44 = !DILocalVariable(name: "it", scope: !15, file: !5, line: 13, type: !45) -!45 = !DICompositeType(tag: DW_TAG_structure_type, name: "VectorIterator", scope: !5, file: !5, line: 260, size: 192, align: 8, flags: DIFlagTypePassByReference | DIFlagNonTrivial, elements: !46, identifier: "struct.VectorIterator") +!45 = !DICompositeType(tag: DW_TAG_structure_type, name: "VectorIterator", scope: !5, file: !5, line: 256, size: 192, align: 8, flags: DIFlagTypePassByReference | DIFlagNonTrivial, elements: !46, identifier: "struct.VectorIterator") !46 = !{!47, !49} -!47 = !DIDerivedType(tag: DW_TAG_member, name: "vector", scope: !45, file: !5, line: 261, baseType: !48, size: 64, offset: 64) +!47 = !DIDerivedType(tag: DW_TAG_member, name: "vector", scope: !45, file: !5, line: 257, baseType: !48, size: 64, offset: 64) !48 = !DIDerivedType(tag: DW_TAG_reference_type, baseType: !28, size: 64) -!49 = !DIDerivedType(tag: DW_TAG_member, name: "cursor", scope: !45, file: !5, line: 262, baseType: !33, size: 64, offset: 128) +!49 = !DIDerivedType(tag: DW_TAG_member, name: "cursor", scope: !45, file: !5, line: 258, baseType: !33, size: 64, offset: 128) !50 = !DILocation(line: 13, column: 5, scope: !15) !51 = !DILocation(line: 14, column: 12, scope: !15) !52 = !DILocation(line: 15, column: 12, scope: !15) diff --git a/test/test-files/std/data/deque-normal-usecase/cout.out b/test/test-files/std/data/deque-normal-usecase/cout.out new file mode 100644 index 000000000..240b07048 --- /dev/null +++ b/test/test-files/std/data/deque-normal-usecase/cout.out @@ -0,0 +1,2 @@ +Size: 6, Capacity: 10 +Hello! \ No newline at end of file diff --git a/test/test-files/std/data/deque-normal-usecase/source.spice b/test/test-files/std/data/deque-normal-usecase/source.spice new file mode 100644 index 000000000..782946663 --- /dev/null +++ b/test/test-files/std/data/deque-normal-usecase/source.spice @@ -0,0 +1,16 @@ +import "std/data/deque"; + +f main() { + Deque q1 = Deque(); + q1.pushBack('l'); + q1.pushBack('l'); + q1.pushFront('e'); + q1.pushBack('o'); + q1.pushFront('H'); + q1.pushBack('!'); + + printf("Size: %d, Capacity: %d\n", q1.getSize(), q1.getCapacity()); + while (!q1.isEmpty()) { + printf("%c", q1.popFront()); + } +}