The Chaos that is Object Oriented Programming
Posted: Mon Dec 22, 2008 12:24 pm
A while back I found a solution of natively using a simple object oriented type of programming found here: http://www.purebasic.fr/english/viewtopic.php?t=27986
That set of code has matured over the years internally, I've never released it, and I have always wanted to expand so that it had a better and more simple way of defining the object and all of its counterparts. I think I've come to a partial solution to the problem that I have obsessed over for a while. It took me a few days to write and debug the main part of the code, there is only a slight problem that doesn't have a nice work around but it all works out. It is in a kind of beta due to it's nature of the code and I can't test every case given the code is very young I hope that I can refine it through your guy's opinions, even being that this raw code is pretty ugly, the end-user's code is not so. Personally I find that this is acceptable code but I wanted this to be used by more that just internal use so that is why I am bouncing this around here.
Does your head hurt yet? Or do you haven't a clue what this code actually does or how to use it.
This is were I explain all of that
Given the example that we wanted to make a home brewed Linked list. We define the object as such:
now theres a lot there but here is the important parts
"Object(LinkedList)" or "Object(<ObjectName>)" is what starts the defining of the object it also sets up some extra macros to allow method and property defining now one of the awkwardness that comes from this is that fact the macros created in beginning of the object definition and must be unique other wise the compiler crashes. I put some simple checks to stop that however, you can possibly wind up crashing the compiler one way or another using this code, I have many times. "LinkedlistMethods" or "<Object Name>Methods" starts the definitions of methods the same goes for "<ObjectName>Properties" there are "End<ObjectName>Methods" and "End<ObjectName>Properties" for there respective blocks. It doesn't matter which way you define the methods and properties (methods first or last), my preference is Methods then Properties. You can also extend other you methods and properties independently of each other to your other object with the "MethodExtends(<ObjectName>)" and "PropertyExtends(<ObjectName>)" being that "<ObjectName>" is the name of the object your extending from. We finish off with "EndObject(<ObjectName>)" keyword which creates some of the other base structures and interfaces that complete the object, which usually not needed but in some extreme case you wanted to alter the base of the object (inheritance or polymorphisms) those structures are defined there.
Now onwards to the method definition, just to state that this is were I could find a solution to an ugly outcome of me trying to simplify the method generation code I wrote to accompany some of my hand written methods. here is a pulled example method from the Linked List libary:
Never mind the code with in the procedure and take notice to the "Method" and "EndMethod" Keywords. Method is a general wrapper for procedure just a bit more complex and some stuff tacked on the top and bottom of it. so the general definition of the method is "Method(<ObjectName>, <MethodName>, <Parameters>)"
MethodName being what is called from the interface (method definition), and parameters being a list of parameters that get passed to the method during the call. Parameters gives me the most grief because of it's limitation. Zero and single parameter methods work just fine how ever 2 and more you have to use the macro i have defined; "Comma" to separate the parameters as shown in this example. I try to stick to my simple parameter definitions
.
There are a few end-user variables that are auto defined within the method: There is "*Base" this variable is the base pointer and structure to the entire object holds the method pointer, properties, size, vector table, ect. "This" this holds the interface for the object you can call other methods of your own object some times handy. "*this" this variable hold the pointer to your object's properties where you can access them like any other structure.
Again to complete the wrap we have "EndMethod".
Moving along, Constructors and Destructors, These guys are pretty straight forward, they the do the preassigning or invalidating all you need to do is write the code that execute when they are constructed and Destructed. Constructors and Destructors are procedures that are wrapped with extra code to complete the setup of the object, you could in theory have a "Null" constructor or Destructor just using the wrappers around empty white space and let the code already written handle the rest. These constructors also do not need to be in any place in the code as long as they are after the object definition (it requires the structures and interfaces defined with in the "Object" block). Also these functions also have the predefined variables that methods hold (*Base, *this, This).
here is how the linked list library handles the constructing and destructing:
Now for the easy part (well sorta of), To create variable I've created the keyword "New" for this. It Create a series of variables that you can use to modify your object nicely, to create a new object you use: "New(<VariableName>, <ObjectName>, <ConstructorParameter = #NULL>, <AutoConstruct = #True>)"
The first 2 parameters are required and for the most part you only need the first 3 but the fourth parameters is just there to give the option of constructing later. New's variables for that are created from the macro are defined by name but are similar to how methods predefined variables are named, as en example we will create a new object named "MyList" when we use the new keyword it will create; "__MyList" which is the absolute base for the object and holds everthing, "*MyList" that will hold the pointer to the properties and how you would normally access yuor objects properties, and "MyList" which will hold the methods for your object.
If you need to use a variable in a structure I have a macro for this which comes in handy in many situations; "StructureObject(<FieldName>, <ObjectName>)" Field name being the name of the field in the structure. This will create the normal 3 variables (or in this case fields), like new does but doesn't construct them until you use NewInto (ex: NewInto(MyStructuredLists\ListA, LinkedList))
if you need to free your object you would use "<YourObject>\Destruct(<Parameter> = #NULL)"
or "Delete(<Variable>, <Parameter = #NULL>)" that does the same thing essentially but also NULL's the other variables for you to prevent weird accessing of released objects. There is another keyword that deletes but for structured objects; "DeleteFrom(<Variable>, <Parameter = #NULL>).
There are a few helper macros that are in the object base file here are a brief description for each of them:
"ConstructorFail" this keyword is there for simple garbage collection if your constructor failed to initialize properly
"DeclareConstructor(<ObjectName>)" pretty self explanatory works like normal declare except it is for the constructor.
"DeclareDestructor(<ObjectName>)" same but for destructor
"Copy(<SourceVariable>, <DestinationVariable>)" this copies an object to another identical object with all of it's object (no cross object copies) and alters the base to reflect the new object.
So with all that out of the way the here is the full source to the linked list library this should be pretty stable if anything crashes here it's probably the Object Base stuff.
and here is a small app demonstrating both now all this does is loads some ascii art character by character into a multidimensional dynamic linked list and displays it back into a console. it's a horrible application but displays the ease of using such a system.
Like I said at the beginning please tell me how you guys think about this code and to be honest I use it all the time and for some people I can understand if this is way too much for what you do or need but, I like to work with large code projects and using objects tend too help manage the code, for smaller things things, like quick apps or demos, this can be overkill and has a bit of overhead memory wise but I think that outwieghts the matter of how the memory is manage in PB rather than in the OS level like the older version of the object base.
That set of code has matured over the years internally, I've never released it, and I have always wanted to expand so that it had a better and more simple way of defining the object and all of its counterparts. I think I've come to a partial solution to the problem that I have obsessed over for a while. It took me a few days to write and debug the main part of the code, there is only a slight problem that doesn't have a nice work around but it all works out. It is in a kind of beta due to it's nature of the code and I can't test every case given the code is very young I hope that I can refine it through your guy's opinions, even being that this raw code is pretty ugly, the end-user's code is not so. Personally I find that this is acceptable code but I wanted this to be used by more that just internal use so that is why I am bouncing this around here.
Code: Select all
;////////////////////////////////////////////////////////////////
;//
;// Title: Object Base
;// Author: Steven "Dreglor" Garcia
;// Version: 2.0.0.0
;// Date: 12/22/08
;// Notes: Allows for a nice (almost) clean object based style of
;// of coding. It's quite a feat of hacking the PB language.
;//
;// Warning This Code is Considered Harmful.
;//
;////////////////////////////////////////////////////////////////
;- Prototype
Prototype __Structers(*Base.i, Parameters.i)
;- Macros
Macro _BeginMacro1
Mac;
EndMacro
Macro _BeginMacro2
ro;
EndMacro
Macro BeginMacro ;Macros within macros, madness! (Thanks to technicorn)
:_BeginMacro1#_BeginMacro2
EndMacro
Macro _EndOfMacro1
EndM;
EndMacro
Macro _EndOfMacro2
acro;
EndMacro
Macro EndOfMacro ;to solve the multimacro with macro problem :D
:_EndOfMacro1#_EndOfMacro2
EndMacro
Macro DoubleQuote ;fix for the macro continuation operator (#) weridness
"
EndMacro
Macro Comma ;the ugliness comes from this
,
EndMacro
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64 ;interface offsets have a bug up to 4.30 final
Macro MethodOffsetOf(Value) ;fred has called this one "fixed"
(OffsetOf(Value) * 2) ;reutnrs the correct offset
EndMacro
CompilerElse
Macro MethodOffsetOf(Value)
(OffsetOf(Value)) ;32bit is just fine
EndMacro
CompilerEndIf
Macro Object(ObjectName) ;begin the definition
CompilerIf Defined(__#ObjectName#_Declared, #PB_Constant)
CompilerError DoubleQuote#ObjectName#DoubleQuote + " Is already a defined object"
CompilerEndIf
#__#ObjectName#_Declared = #True
BeginMacro ObjectName#Methods ;begin method (interface) definitions
CompilerIf Defined(__#ObjectName#_Methods, #PB_Constant)
CompilerError DoubleQuote#ObjectName#DoubleQuote + " has already had it's method interfaces defined"
CompilerEndIf
#__#ObjectName#_Methods = #True
Interface __#ObjectName#_Methods
EndOfMacro
BeginMacro End#ObjectName#Methods ;end the method defintion and define the vector table
EndInterface
Structure __#ObjectName#_VectorTable
Address.i[SizeOf(__#ObjectName#_Methods)/SizeOf(integer)]
EndStructure
Global __#ObjectName#_VectorTable.__#ObjectName#_VectorTable
EndOfMacro
BeginMacro ObjectName#Properties ;start the beginning of the properties (structure)
CompilerIf Defined(__#ObjectName#_Properties, #PB_Constant)
CompilerError DoubleQuote#ObjectName#DoubleQuote + " has already had it's method interfaces defined"
CompilerEndIf
#__#ObjectName#_Properties = #True
Structure __#ObjectName#_Properties
EndOfMacro
BeginMacro End#ObjectName#Properties ;end the property definition
EndStructure
EndOfMacro
EndMacro
Macro MethodExtends(ObjectName) ;if you ever want to extend your methods here you go
Extends __#ObjectName#_Methods
EndMacro
Macro PropertyExtends(ObjectName) ;...or properties
Extends __#ObjectName#_Properties
EndMacro
Macro EndObject(ObjectName) ;finish up the object definition
CompilerIf Defined(__#ObjectName#_Declared, #PB_Constant) = #False
CompilerError "End Object with out a Begin Object"
CompilerEndIf
Interface ObjectName Extends __#ObjectName#_Methods ;add on the contructors
Construct(*Parameters.i = #NULL)
Destruct(*Parameters.i = #NULL)
EndInterface
Structure ObjectName#_ ;and get the general object definition up
*MethodPointer.i
Properties.__#ObjectName#_Properties
Size.i
*Self.__#ObjectName#_Methods
VectorTable.i[SizeOf(__#ObjectName#_Methods)/SizeOf(integer)]
Constructor.__Structers
Destructor.__Structers
EndStructure
EndMacro
Macro StructureObject(Field, ObjectName) ;for putting objects into structures (with access to its methods and properties)
Field#__.ObjectName#_
Field.ObjectName
*Field#_.__#ObjectName#_Properties
EndMacro
Macro Constructor(ObjectName) ;contructor wrap
CompilerIf Defined(__#ObjectName#_Constructor, #PB_Procedure)
CompilerError "Constructor for " + DoubleQuote#ObjectName#DoubleQuote + " has already been defined"
CompilerEndIf
Declare __#ObjectName#_Destructor(*Base.ObjectName#_, Parameter.i = #NULL)
Procedure __#ObjectName#_Constructor(*Base.ObjectName#_, Parameter.i = #NULL)
If *Base = #NULL
ProcedureReturn #NULL
EndIf
Protected Base.ObjectName = *Base
Protected This.__#ObjectName#_Methods = *Base
Protected *This.__#ObjectName#_Properties = @*Base\Properties
*Base\MethodPointer = @*Base\VectorTable
*Base\Size = SizeOf(ObjectName)
*Base\Self = *Base
CopyMemory(__#ObjectName#_VectorTable, @*Base\VectorTable, SizeOf(__#ObjectName#_Methods))
*Base\Constructor.__Structers = @__#ObjectName#_Constructor()
*Base\Destructor.__Structers = @__#ObjectName#_Destructor()
EndMacro
Macro EndConstructor
ProcedureReturn *Base
EndProcedure
EndMacro
Macro ConstructorFail ;if failure, collect garbage
Base\Destructor()
ProcedureReturn #NULL
EndMacro
Macro DeclareConstructor(ObjectName) ;if you need to declare your constructors...
Declare __#ObjectName#_Constructor(*Base.ObjectName#_, Parameter.i = #NULL)
EndMacro
Macro IsConstructed(ObjectName) ;if auto contruct has been turned off
(Not (ObjectName\VectorTable[0] <> 0))
EndMacro
Macro Destructor(ObjectName) ;destructor wrap
CompilerIf Defined(__#ObjectName#_Destructor, #PB_Constant)
CompilerError "Destructor for " + DoubleQuote#ObjectName#DoubleQuote + " has already been defined"
CompilerEndIf
#__#ObjectName#_Destructor = #True
Procedure __#ObjectName#_Destructor(*Base.ObjectName#_, Parameter.i = #NULL)
If *Base = #NULL
ProcedureReturn #NULL
EndIf
Protected Base.ObjectName = *Base
Protected This.__#ObjectName#_Methods = *Base\Properties
Protected *This.__#ObjectName#_Properties = @*Base\Properties
EndMacro
Macro EndDestructor
ZeroMemory_(*This, *Base\Size)
ProcedureReturn *This
EndProcedure
EndMacro
Macro DeclareDestructor(ObjectName)
Declare __ObjectName#_Destructor(*Base.ObjectName#_, Parameter.i = #NULL)
EndMacro
Macro Method(ObjectName, Method, Parameters = __NULL = #NULL) ;when starting a new method this has to be used (commits the methods to the vector table) as well as defining a few variables to use
CompilerIf Defined(__#ObjectName#_#Method#, #PB_Procedure)
CompilerError DoubleQuote#Method#DoubleQuote + " has already been defined for object " + DoubleQuote#ObjectName#DoubleQuote
CompilerEndIf
Declare __#ObjectName#_#Method#(*Base.ObjectName#_,Parameters)
__#ObjectName#_VectorTable\Address[MethodOffsetOf(__#ObjectName#_Methods\Method()) / SizeOf(integer)] = @__#ObjectName#_#Method#()
Procedure __#ObjectName#_#Method#(*Base.ObjectName#_,Parameters)
Protected This.__#ObjectName#_Methods = *Base
Protected *This.__#ObjectName#_Properties = @*Base\Properties
EndMacro
Macro EndMethod
EndProcedure
EndMacro
Macro New(Variable, ObjectName, Parameter = #NULL, AutoConstruct = #True) ;when defineing a new varable using a object use this
__#Variable.ObjectName#_
__#Variable\MethodPointer = @__#Variable\VectorTable
__#Variable\Size = SizeOf(ObjectName#_)
__#Variable\Self = __#Variable
__#Variable\Constructor = @__#ObjectName#_Constructor()
__#Variable\Destructor = @__#ObjectName#_Destructor()
*Variable.__#ObjectName#_Properties = @__#Variable\Properties
Variable.ObjectName = __#Variable
If AutoConstruct
Variable\Construct(Parameter)
EndIf
EndMacro
Macro NewInto(Variable, ObjectName, Parameter = #NULL, AutoConstruct = #True) ;put a object into a predefined structure
Variable = Variable#__
Variable#_ = Variable + OffsetOf(ObjectName#_\Properties )
Variable#__\MethodPointer = Variable + OffsetOf(ObjectName#_\VectorTable)
Variable#__\Size = SizeOf(ObjectName#_)
Variable#__\Self = Variable#__
Variable#__\Constructor = @__#ObjectName#_Constructor()
Variable#__\Destructor = @__#ObjectName#_Destructor()
If AutoConstruct
Variable\Construct(Parameter)
EndIf
EndMacro
Macro Copy(SourceVariable, DestinationVariable)
CopyMemory(SourceVariable, DestinationVariable, SizeOf(SourceVariable))
*__DestinationVariable\MethodPointer = @__DestinationVariable\VectorTable
*__DestinationVariable\Self = *__DestinationVariable
EndMacro
Macro Delete(Variable, Parameter = #NULL) ;Destroy the created object
Variable\Destruct()
Variable = #NULL
*Variable = #NULL
*__#Variable = #NULL
EndMacro
Macro DeleteFrom(Variable, Parameter = #NULL) ;Delete the object from the structure
Variable\Destruct()
Variable = #NULL
Variable#_ = #NULL
EndMacro
This is were I explain all of that

Given the example that we wanted to make a home brewed Linked list. We define the object as such:
Code: Select all
Object(LinkedList)
LinkedlistMethods
Index() ;Get the Index of current Element
Count() ;Count the Elements that are containted in the list
Reset() ;Reset the Current Element so that the current element is not set or NULL
Clear() ;deletes all of the elements that are in the list
FirstElement() ;assigns the current element as the first element
NextElement() ;assigns the current element as the next element in the list
PreviousElement() ;assigns the current element as the previous element in the list
ChangeCurrentElement(*NewCurrent.Element) ;assigns the current element as the element pointed in the parameter
SelectElement(Index.i) ;changes the current element to reflect that of the given index
LastElement() ;sets the current element to the last element in the list
AddElement() ;adds a new element after the current element in the list
InsertElement() ;inserts a new element before the current element in the list
DeleteElement(Flag.l = #False) ;deletes the current element
SwapElements(*ElementA.Element, *ElementB.Element) ;swaps element positions in the list
GetElement() ;returns the pointer of the current element
SetElement(DataPointer.i) ;copies the data of the given point to the current element
Search(*ComparePointer.i) ;compares the data in each element to the given data and returns if found a match
Save(File.s) ;saves the linked list state and data into a file
Load(File.s) ;loads the linked list state and data into the list
IsElement(*Element.Element) ;checks of given pointer is a vaild element
DebugHead() ;debugs the linked list object structure
DebugRange(FromIndex.i = #NULL, ToIndex.i = #PB_Any) ;debug a range of elements structures
EndLinkedlistMethods
LinkedlistProperties
ListIndex.i ;holds the current index of the list
ListCount.i ;holds the number of elements that are in the list
Size.i ;memory alloted for the elements (not including the headers)
*First.Element ;pointer to the first element
*Current.Element ;pointer to the current element
*Last.Element ;pointer to the last element
EndLinkedListProperties
EndObject(LinkedList)
"Object(LinkedList)" or "Object(<ObjectName>)" is what starts the defining of the object it also sets up some extra macros to allow method and property defining now one of the awkwardness that comes from this is that fact the macros created in beginning of the object definition and must be unique other wise the compiler crashes. I put some simple checks to stop that however, you can possibly wind up crashing the compiler one way or another using this code, I have many times. "LinkedlistMethods" or "<Object Name>Methods" starts the definitions of methods the same goes for "<ObjectName>Properties" there are "End<ObjectName>Methods" and "End<ObjectName>Properties" for there respective blocks. It doesn't matter which way you define the methods and properties (methods first or last), my preference is Methods then Properties. You can also extend other you methods and properties independently of each other to your other object with the "MethodExtends(<ObjectName>)" and "PropertyExtends(<ObjectName>)" being that "<ObjectName>" is the name of the object your extending from. We finish off with "EndObject(<ObjectName>)" keyword which creates some of the other base structures and interfaces that complete the object, which usually not needed but in some extreme case you wanted to alter the base of the object (inheritance or polymorphisms) those structures are defined there.
Now onwards to the method definition, just to state that this is were I could find a solution to an ugly outcome of me trying to simplify the method generation code I wrote to accompany some of my hand written methods. here is a pulled example method from the Linked List libary:
Code: Select all
Method(LinkedList, SwapElements, *ElementA.Element Comma *ElementB.Element) ;swaps element (by location)
If *ElementA = #NULL Or *ElementB = #NULL
ProcedureReturn #False
EndIf
*ElementA - OffsetOf(Element\Element) ;Set Real element
*ElementB - OffsetOf(Element\Element) ;Set Real element
;swaps only the location not the data
;problem would only arise if the swapped element is the current element or if the user was using the
;absolute memory locations to access the elements
Swap *ElementA\Next, *ElementB\Next
Swap *ElementA\Previous, *ElementA\Previous
ProcedureReturn #True
EndMethod
MethodName being what is called from the interface (method definition), and parameters being a list of parameters that get passed to the method during the call. Parameters gives me the most grief because of it's limitation. Zero and single parameter methods work just fine how ever 2 and more you have to use the macro i have defined; "Comma" to separate the parameters as shown in this example. I try to stick to my simple parameter definitions

There are a few end-user variables that are auto defined within the method: There is "*Base" this variable is the base pointer and structure to the entire object holds the method pointer, properties, size, vector table, ect. "This" this holds the interface for the object you can call other methods of your own object some times handy. "*this" this variable hold the pointer to your object's properties where you can access them like any other structure.
Again to complete the wrap we have "EndMethod".
Moving along, Constructors and Destructors, These guys are pretty straight forward, they the do the preassigning or invalidating all you need to do is write the code that execute when they are constructed and Destructed. Constructors and Destructors are procedures that are wrapped with extra code to complete the setup of the object, you could in theory have a "Null" constructor or Destructor just using the wrappers around empty white space and let the code already written handle the rest. These constructors also do not need to be in any place in the code as long as they are after the object definition (it requires the structures and interfaces defined with in the "Object" block). Also these functions also have the predefined variables that methods hold (*Base, *this, This).
here is how the linked list library handles the constructing and destructing:
Code: Select all
Constructor(LinkedList) ;Creates a new instance of a linked list
;Setup Properties
*This\ListIndex = -1 ; negitive is not vaild
*This\ListCount = 0
*This\Size = Parameter
*This\First = #NULL
*This\Current = #NULL
*This\Last = #NULL
EndConstructor
Destructor(LinkedList);destroys a specified instance of a linked list
;Destroy Elements
If Parameter = #False
This\Clear()
EndIf
;Null Properties
*This\ListIndex = 0
*This\ListCount = 0
*This\Size = 0
*This\First = #NULL
*This\Current = #NULL
*This\Last = #NULL
EndDestructor
The first 2 parameters are required and for the most part you only need the first 3 but the fourth parameters is just there to give the option of constructing later. New's variables for that are created from the macro are defined by name but are similar to how methods predefined variables are named, as en example we will create a new object named "MyList" when we use the new keyword it will create; "__MyList" which is the absolute base for the object and holds everthing, "*MyList" that will hold the pointer to the properties and how you would normally access yuor objects properties, and "MyList" which will hold the methods for your object.
If you need to use a variable in a structure I have a macro for this which comes in handy in many situations; "StructureObject(<FieldName>, <ObjectName>)" Field name being the name of the field in the structure. This will create the normal 3 variables (or in this case fields), like new does but doesn't construct them until you use NewInto (ex: NewInto(MyStructuredLists\ListA, LinkedList))
if you need to free your object you would use "<YourObject>\Destruct(<Parameter> = #NULL)"
or "Delete(<Variable>, <Parameter = #NULL>)" that does the same thing essentially but also NULL's the other variables for you to prevent weird accessing of released objects. There is another keyword that deletes but for structured objects; "DeleteFrom(<Variable>, <Parameter = #NULL>).
There are a few helper macros that are in the object base file here are a brief description for each of them:
"ConstructorFail" this keyword is there for simple garbage collection if your constructor failed to initialize properly
"DeclareConstructor(<ObjectName>)" pretty self explanatory works like normal declare except it is for the constructor.
"DeclareDestructor(<ObjectName>)" same but for destructor
"Copy(<SourceVariable>, <DestinationVariable>)" this copies an object to another identical object with all of it's object (no cross object copies) and alters the base to reflect the new object.
So with all that out of the way the here is the full source to the linked list library this should be pretty stable if anything crashes here it's probably the Object Base stuff.
Code: Select all
;////////////////////////////////////////////////////////////////
;//
;// Title: Linked List Libary
;// FileName: LinkedList.pbi
;// Version: 2.2.0.4
;// Date: 12/21/08
;// Notes: Provide a dynamic linked list for all apps
;//
;////////////////////////////////////////////////////////////////
;- Includes
XIncludeFile "ObjectBase.pbi"
;- Classes
;{ Linked List Class
;- Constants
#LinkedListMarker = "DLL2204"
;- Structures
Structure ElementAccess
StructureUnion
Characters.c[0]
Bytes.b[0]
Words.w[0]
Longs.l[0]
Floats.f[0]
Integers.i[0]
Quads.q[0]
Doubles.d[0]
EndStructureUnion
EndStructure
Structure Element
*Next.Element
*Previous.Element
Element.ElementAccess ;Flexible Read/Write Pointer
;^^^ Purely abstract
EndStructure
;- Objects
Object(LinkedList)
LinkedlistMethods
Index() ;Get the Index of current Element
Count() ;Count the Elements that are containted in the list
Reset() ;Reset the Current Element so that the current element is not set or NULL
Clear() ;deletes all of the elements that are in the list
FirstElement() ;assigns the current element as the first element
NextElement() ;assigns the current element as the next element in the list
PreviousElement() ;assigns the current element as the previous element in the list
ChangeCurrentElement(*NewCurrent.Element) ;assigns the current element as the element pointed in the parameter
SelectElement(Index.i) ;changes the current element to reflect that of the given index
LastElement() ;sets the current element to the last element in the list
AddElement() ;adds a new element after the current element in the list
InsertElement() ;inserts a new element before the current element in the list
DeleteElement(Flag.l = #False) ;deletes the current element
SwapElements(*ElementA.Element, *ElementB.Element) ;swaps element positions in the list
GetElement() ;returns the pointer of the current element
SetElement(DataPointer.i) ;copies the data of the given point to the current element
Search(*ComparePointer.i) ;compares the data in each element to the given data and returns if found a match
Save(File.s) ;saves the linked list state and data into a file
Load(File.s) ;loads the linked list state and data into the list
IsElement(*Element.Element) ;checks of given pointer is a vaild element
DebugHead() ;debugs the linked list object structure
DebugRange(FromIndex.i = #NULL, ToIndex.i = #PB_Any) ;debug a range of elements structures
EndLinkedlistMethods
LinkedlistProperties
ListIndex.i ;holds the current index of the list
ListCount.i ;holds the number of elements that are in the list
Size.i ;memory alloted for the elements (not including the headers)
*First.Element ;pointer to the first element
*Current.Element ;pointer to the current element
*Last.Element ;pointer to the last element
EndLinkedListProperties
EndObject(LinkedList)
;- Macros
Macro ForAllElements(LinkedList, Element)
Element = LinkedList\FirstElement()
While Element <> #NULL
EndMacro
Macro ForAllEnd(LinkedList, Element)
Element = LinkedList\NextElement()
Wend
EndMacro
;- Constructors
Constructor(LinkedList) ;Creates a new instance of a linked list
;Setup Properties
*This\ListIndex = -1 ; negitive is not vaild
*This\ListCount = 0
*This\Size = Parameter
*This\First = #Null
*This\Current = #Null
*This\Last = #Null
EndConstructor
Destructor(LinkedList);destroys a specified instance of a linked list
;Destroy Elements
If Parameter = #False
This\Clear()
EndIf
;Null Properties
*This\ListIndex = 0
*This\ListCount = 0
*This\Size = 0
*This\First = #Null
*This\Current = #Null
*This\Last = #Null
EndDestructor
;- Methods
Method(LinkedList, Index) ;Get the current element index in the list (Fast, Cached)
ProcedureReturn *This\ListIndex
EndMethod
Method(LinkedList, Count) ;returns the number of elements in the list (Fast, Cached)
ProcedureReturn *This\ListCount
EndMethod
Method(LinkedList, Reset) ;Resets the current list element to be before the first element
*This\Current = #Null
EndMethod
Method(LinkedList, Clear) ;Clears the List if elements
;For Each Element
*This\Current = *This\First
While *This\Current <> #Null
NextElement = *This\Current\Next
FreeMemory(*This\Current)
*This\Current = NextElement
Wend
*This\ListIndex = -1
*This\ListCount = 0
*This\First = #Null
*This\Current = #Null
*This\Last = #Null
EndMethod
Method(LinkedList, FirstElement) ;changes the current element to the first element
*This\Current = *This\First
If *This\Current = #Null
ProcedureReturn #Null
Else
ProcedureReturn *This\Current\Element
EndIf
EndMethod
Method(LinkedList, NextElement) ;changes the current element to the next element
If *This\Current <> #Null And *This\Current\Next <> #Null
*This\Current = *This\Current\Next
ProcedureReturn *This\Current\Element
EndIf
ProcedureReturn #Null
EndMethod
Method(LinkedList, PreviousElement) ;changes the current element to the previous element
If *This\Current <> #Null And *This\Current\Previous <> #Null
*This\Current = *This\Current\Previous
ProcedureReturn *This\Current\Element
EndIf
ProcedureReturn #Null
EndMethod
Method(LinkedList, ChangeCurrentElement, *NewCurrent.Element) ;changes the current element to the a user specified element via address
If *NewCurrent = #Null
*This\Current = #Null
*This\ListIndex = -1
Else
*NewCurrent - OffsetOf(Element\Element) ;Set to Real Element
;For Each Element
*This\Current = *This\First
Found = #False
Index = 0
Repeat
If *This\Current = *NewCurrent
Found = #True
Break
EndIf
Index + 1
*This\Current = *This\Current\Next
Until *This\Current = #Null
If Found = #False
ProcedureReturn #False
EndIf
*This\ListIndex = Index
*This\Current = *NewCurrent
EndIf
ProcedureReturn #True
EndMethod
Method(LinkedList, SelectElement, Index.i) ;changes the current element to the specified index
If Index < 0 Or Index > *This\ListCount
ProcedureReturn #Null
EndIf
;Enumerate Elements
*This\Current = *This\First
For Element = 0 To Index
*This\Current = *This\Current\Next
Next
ProcedureReturn *This\Current\Element
EndMethod
Method(LinkedList, LastElement)
*This\Current = *This\Last
If *This\Current = #Null
ProcedureReturn #Null
Else
ProcedureReturn *This\Current\Element
EndIf
EndMethod
Method(LinkedList, AddElement) ;adds a element after the current element
*New.Element = AllocateMemory(SizeOf(Element) + *This\Size)
If *This\Current = #Null
If *This\First = #Null ;if in a Empty List
;[New]
;This Element
*New\Next = #Null
*New\Previous = #Null
;no current element to alter
;Base
*This\Current = *New
*This\First = *New
*This\Last = *New
Else ;if at the beginning of a list
;[New][Element1][...]
;This Element
*New\Next = *This\First ;New Pointing To Element1
*New\Previous = #Null
;Current Element
*This\First\Previous = *New ;Element1 pointing to New
;Base
*This\Current = *New ;Change the Current Element
*This\First = *New ;Change the First Element
EndIf
Else
If *This\Current = *This\Last ;If at end of the list
;[Element1][New][...]
;This Element
*New\Next = #Null
*New\Previous = *This\Last ;New pointing to Element1
;Current Element
*This\Last\Next = *New ;Element1 pointing to New
;Base
*This\Current = *New ;Change the Current Element
*This\Last = *New ;Change the First Element
Else ;if somewhere in the middle of the list
;[...][Element1][New][Element2][...]
;This Element
*New\Next = *This\Current\Next ;Element2
*New\Previous = *This\Current ;Element1
;Current Element
*This\Current\Next\Previous = *New ;Element2 Pointing to New
*This\Current\Next = *New ;Element1 Pointing to New
;Base
*This\Current = *New ;Change the Current Element
EndIf
EndIf
*This\ListIndex + 1 ;Placing the element 1 ahead of the current
*This\ListCount + 1 ;always adding a new element
ProcedureReturn *This\Current\Element
EndMethod
Method(LinkedList, InsertElement) ;inserts a new element before the current element
*New.Element = AllocateMemory(SizeOf(Element) + *This\Size)
If *This\Current = #Null
If *This\First = #Null ;if in a Empty List
;[New]
;This Element
*New\Next = #Null
*New\Previous = #Null
;no current element to alter
;Base
*This\Current = *New
*This\First = *New
*This\Last = *New
Else ;if at the beginning of a list
;[New][Element1][...]
;This Element
*New\Next = *This\First ;New Pointing To Element1
*New\Previous = #Null
;Current Element
*This\First\Previous = *New ;Element1 pointing to New
;Base
*This\Current = *New ;Change the Current Element
*This\First = *New ;Change the First Element
EndIf
*This\ListIndex = 0
Else ;if somewhere in the middle of the list or the end
If *This\Current = *This\First ;at the beginning
;[New][Element1][Element2][...]
;This Element
*New\Next = *This\Current ;Element2
*New\Previous = #Null ;Element1
;Current Element
*This\Current\Previous = *New
;Base
*This\Current = *New ;Change the Current Element
*This\First = *New ;Change the First Element
*This\ListIndex = 0
Else ;middle or end
;[...][New][Element1][Element2][...]
;This Element
*New\Next = *This\Current ;Element2
*New\Previous = *This\Current\Previous ;Element1
;Current Element
*This\Current\Previous\Next = *New ;Element2 Pointing to New
*This\Current\Previous = *New ;Element1 Pointing to New
;Base
*This\Current = *New ;Change the Current Element
*This\ListIndex - 1
EndIf
EndIf
*This\ListCount + 1 ;always adding a new element
ProcedureReturn *This\Current\Element
EndMethod
Method(LinkedList, DeleteElement, flags.c) ;deletes a element
If *This\Current = #Null ;if no elements to delete or if there is no current element
ProcedureReturn #Null
EndIf
;patch surrounding elements
If *This\Current\Next <> #Null ;If Last
*This\Current\Next\Previous = *This\Current\Previous
Else
*This\Last = *This\Current\Previous
EndIf
If *This\Current\Previous <> #Null ;If First
*This\Current\Previous\Next = *This\Current\Next
Else
*This\First = *This\Current\Next
EndIf
ElementToBeDeleted = *This\Current
If flags = 0
*This\Current = *This\Current\Previous
*This\ListIndex - 1
;works like backspace button on keyboard
Else
*This\Current = *This\Current\Next
;works like delete button on keyboard
EndIf
FreeMemory(ElementToBeDeleted)
*This\ListCount - 1 ;deleting an element
If *This\Current = #Null
ProcedureReturn #Null
Else
ProcedureReturn *This\Current\Element
EndIf
EndMethod
Method(LinkedList, SwapElements, *ElementA.Element Comma *ElementB.Element) ;swaps element (by location)
If *ElementA = #Null Or *ElementB = #Null
ProcedureReturn #False
EndIf
*ElementA - OffsetOf(Element\Element) ;Set Real element
*ElementB - OffsetOf(Element\Element) ;Set Real element
;swaps only the location not the data
;problem would only arise if the swapped element is the current element or if the user was using the
;absolute memory locations to access the elements
Swap *ElementA\Next, *ElementB\Next
Swap *ElementA\Previous, *ElementA\Previous
ProcedureReturn #True
EndMethod
Method(LinkedList, GetElement) ;retrives the current element data pointer
If *This\Current <> #Null
ProcedureReturn *This\Current\Element
Else
ProcedureReturn #Null
EndIf
EndMethod
Method(LinkedList, SetElement, *DataPointer.i) ;copys the data into the current element
If *This\Current <> #Null And DataPointer <> #Null
ProcedureReturn CopyMemory(DataPointer, @*This\Current\Element, SizeOf(*This\Size))
Else
ProcedureReturn #False
EndIf
EndMethod
Method(LinkedList, Search, *ComparePointer.i) ;Finds an element that matches the data compared by the compare pointer
If ComparePointer = #Null Or *This\First = #Null
ProcedureReturn #Null
EndIf
*This\Current = *This\First
Repeat
If CompareMemory(*This\Current\Element, ComparePointer, *This\Size) = #True
ProcedureReturn *This\Current\Element
EndIf
*This\Current = *This\Current\Next
Until *This\Current = #Null
EndMethod
Method(LinkedList, Save, File.s) ;Saves The Current Linked List Into a Empty File
If File = #Null$ Or *This\First = #Null
ProcedureReturn #False
EndIf
FileHandle = CreateFile(#PB_Any, File)
If IsFile(FileHandle) = #Null
ProcedureReturn #False
EndIf
WriteStringN(FileHandle, #LinkedListMarker)
WriteInteger(FileHandle, *This\Size)
*This\Current = *This\First
Repeat
WriteData(FileHandle, *This\Current\Element, *This\Size)
*This\Current = *This\Current\Next
Until *This\Current = #Null
FlushFileBuffers(FileHandle)
CloseFile(FileHandle)
ProcedureReturn #True
EndMethod
Method(LinkedList, Load, File.s) ;Loads the Linked List From File (Clears and Changes the element size if needed)
If File = #NULL$
ProcedureReturn #False
EndIf
;Clear the List
If *This\First <> #NULL
*This\Current = *This\First
Repeat
NextElement = *This\Current\Next
FreeMemory(*This\Current)
*This\Current = NextElement
Until *This\Current = #NULL
EndIf
*This\ListIndex = -1
*This\ListCount = 0
*This\First = #NULL
*This\Current = #NULL
*This\Last = #NULL
FileHandle = ReadFile(#PB_Any, File)
If IsFile(FileHandle) = #NULL
ProcedureReturn #False
EndIf
If ReadString(FileHandle) <> #LinkedListMarker
CloseFile(FileHandle)
ProcedureReturn #False
EndIf
*This\Size = ReadInteger(FileHandle)
Repeat
ReadData(FileHandle, This\AddElement(), *This\Size)
Until Eof(FileHandle) <> #NULL
EndMethod
Method(LinkedList, IsElement, *Element.Element) ;Simple check for any linked list
If *Element <= OffsetOf(Element\Element)
ProcedureReturn #False ;prevents the NULL or SizeOf(INTEGER) problem
EndIf
ProcedureReturn #True
EndMethod
Method(LinkedList, DebugHead)
Debug "------Debugging Linked List-------"
Debug "Instance: $" + RSet(Hex(*This), SizeOf(integer), "0")
Debug "Elements Index: " + Str(*This\ListIndex)
Debug "Number Of Elements: " + Str(*This\ListCount)
Debug "Element Size: " + Str(*This\Size)
Debug "First Element: $" + RSet(Hex(*This\First), SizeOf(integer), "0")
Debug "Current Element: $" + RSet(Hex(*This\Current), SizeOf(integer), "0")
Debug "Last Element: $" + RSet(Hex(*This\Last), SizeOf(integer), "0")
Debug "----------------------------------"
EndMethod
Method(LinkedList, DebugRange, FromIndex = 0 Comma ToIndex = #PB_Any)
If ToIndex <= 0 Or ToIndex > *This\ListCount
ToIndex = *This\ListCount
EndIf
If FromIndex < 0
FromIndex = 0
EndIf
RecallElement = *This\Current
Debug "--Debugging Linked List Elements--"
Debug "Instance: $" + RSet(Hex(*This), SizeOf(integer), "0")
Debug "Range: [" + Str(FromIndex) + " - " + Str(ToIndex) + "]"
*This\Current = *This\First
For Index = 1 To *This\ListCount
Debug ""
Debug "Element Index: " + Str(Index)
Debug "Element: $" + RSet(Hex(*This\Current), SizeOf(integer), "0")
Debug "Next Element: $" + RSet(Hex(*This\Current\Next), SizeOf(integer), "0")
Debug "Previous Element: $" + RSet(Hex(*This\Current\Previous), SizeOf(integer), "0")
If *This\Current\Next = #NULL
Debug "Continutity Error - Next Is Null"
EndIf
If *This\Current\Previous = #NULL
Debug "Continutity Error - Previous Is Null"
EndIf
*This\Current = *This\Current\Next
Next
Debug "----------------------------------"
*This\Current = RecallElement
EndMethod
Code: Select all
;- Includes
XIncludeFile "LinkedList.pbi"
XIncludeFile "ObjectBase.pbi"
;- Structure
Structure Char
c.c
EndStructure
Structure Line
StructureObject(Char, LinkedList)
EndStructure
;- Entry Point
CallDebugger
New(AsciiArt, LinkedList, SizeOf(Line))
For Line = 0 To 34
*CurrentLine.Line = AsciiArt\AddElement()
NewInto(*CurrentLine\Char, LinkedList, SizeOf(Char))
For Char = 0 To 53
*Current.Char = *CurrentLine\Char\AddElement()
*Current\c = Asc(PeekS(?BeginAsciiArt + (Line * 54) + Char, 1))
Next
Next
OpenConsole()
ForAllElements(AsciiArt, *CurrentLine.Line)
ForAllElements(*CurrentLine\Char, *Current.Char)
Print(Chr(*Current\c))
ForAllEnd(*CurrentLine\Char, *Current.Char)
ForAllEnd(AsciiArt, *CurrentLine.Line)
CallDebugger
DataSection
BeginAsciiArt:
Data.s " ,_-=(!7(7/zs_. " + Chr(#CR) + Chr(#LF)
Data.s " .=' ' .`/,/!(=)Zm. " + Chr(#CR) + Chr(#LF)
Data.s " .._,,._.. ,-`- `,\ ` -` -`\\7//WW. " + Chr(#CR) + Chr(#LF)
Data.s " ,v=~/.-,-\- -!|V-s.)iT-|s|\-.' `///mK%. " + Chr(#CR) + Chr(#LF)
Data.s " v!`i!-.e]-g`bT/i(/[=.Z/m)K(YNYi.. /-]i44M. " + Chr(#CR) + Chr(#LF)
Data.s " v`/,`|v]-DvLcfZ/eV/iDLN\D/ZK@%8W[Z.. `/d!Z8m " + Chr(#CR) + Chr(#LF)
Data.s " //,c\(2(X/NYNY8]ZZ/bZd\()/\7WY%WKKW) -'|(][%4." + Chr(#CR) + Chr(#LF)
Data.s " ,\\i\c(e)WX@WKKZKDKWMZ8(b5/ZK8]Z7%ffVM, -.Y!bNMi" + Chr(#CR) + Chr(#LF)
Data.s " /-iit5N)KWG%%8%%%%W8%ZWM(8YZvD)XN(@. [ \]!/GXW[" + Chr(#CR) + Chr(#LF)
Data.s " / ))G8\NMN%W%%%%%%%%%%8KK@WZKYK*ZG5KMi,- vi[NZGM[" + Chr(#CR) + Chr(#LF)
Data.s " i\!(44Y8K%8%%%**~YZYZ@%%%%%4KWZ/PKN)ZDZ7 c=//WZK%!" + Chr(#CR) + Chr(#LF)
Data.s " ,\v\YtMZW8W%%f`,`.t/bNZZK%%W%%ZXb*K(K5DZ -c\\/KM48 " + Chr(#CR) + Chr(#LF)
Data.s " -|c5PbM4DDW%f v./c\[tMY8W%PMW%D@KW)Gbf -/(=ZZKM8[ " + Chr(#CR) + Chr(#LF)
Data.s " 2(N8YXWK85@K -'c|K4/KKK%@ V%@@WD8e~ .//ct)8ZK%8` " + Chr(#CR) + Chr(#LF)
Data.s " =)b%]Nd)@KM[ !'\cG!iWYK%%| !M@KZf -c\))ZDKW%` " + Chr(#CR) + Chr(#LF)
Data.s " YYKWZGNM4/Pb '-VscP4]b@W% 'Mf` -L\///KM(%W! " + Chr(#CR) + Chr(#LF)
Data.s " !KKW4ZK/W7)Z. '/cttbY)DKW% -` .',\v)K(5KW%%f " + Chr(#CR) + Chr(#LF)
Data.s " 'W)KWKZZg)Z2/,!/L(-DYYb54% ,,`, -\-/v(((KK5WW%f " + Chr(#CR) + Chr(#LF)
Data.s " \M4NDDKZZ(e!/\7vNTtZd)8\Mi!\-,-/i-v((tKNGN%W%% " + Chr(#CR) + Chr(#LF)
Data.s " 'M8M88(Zd))///((|D\tDY\\KK-`/-i(=)KtNNN@W%%%@%[ " + Chr(#CR) + Chr(#LF)
Data.s " !8%@KW5KKN4///s(\Pd!ROBY8/=2(/4ZdzKD%K%%%M8@%% " + Chr(#CR) + Chr(#LF)
Data.s " '%%%W%dGNtPK(c\/2\[Z(ttNYZ2NZW8W8K%%%%YKM%M%%. " + Chr(#CR) + Chr(#LF)
Data.s " *%%W%GW5@/%!e]_tZdY()v)ZXMZW%W%%%*5Y]K%ZK%8[ " + Chr(#CR) + Chr(#LF)
Data.s " '*%%%%8%8WK\)[/ZmZ/Zi]!/M%%%%@f\ \Y/NNMK%%! " + Chr(#CR) + Chr(#LF)
Data.s " 'VM%%%%W%WN5Z/Gt5/b)((cV@f` - |cZbMKW%%| " + Chr(#CR) + Chr(#LF)
Data.s " 'V*M%%%WZ/ZG\t5((+)L\'-,,/ -)X(NWW%% " + Chr(#CR) + Chr(#LF)
Data.s " `~`MZ/DZGNZG5(((\, ,t\\Z)KW%@ " + Chr(#CR) + Chr(#LF)
Data.s " 'M8K%8GN8\5(5///]i!v\K)85W%%f " + Chr(#CR) + Chr(#LF)
Data.s " YWWKKKKWZ8G54X/GGMeK@WM8%@ " + Chr(#CR) + Chr(#LF)
Data.s " !M8%8%48WG@KWYbW%WWW%%%@ " + Chr(#CR) + Chr(#LF)
Data.s " VM%WKWK%8K%%8WWWW%%%@` " + Chr(#CR) + Chr(#LF)
Data.s " ~*%%%%%%W%%%%%%%@~ " + Chr(#CR) + Chr(#LF)
Data.s " ~*MM%%%%%%@f` " + Chr(#CR) + Chr(#LF)
Data.s " ''''' " + Chr(#CR) + Chr(#LF)
EndDataSection