Skip to content
Home » Blog » LotusScript Stack data structure

LotusScript Stack data structure

The stack data structure is a collection of items that supports “last in, first out” access. You have two operations: “Push” an item onto the stack, and “Pop” to retrieve and remove the most recently added item. It’s like the stack of plates in a buffet restaurant, where unless you’re a rule-breaking savage, you can only take the plate on top, which was added last.

Compare to a queue, where when you get a value from it, you’re getting the oldest thing it contains.

A stack is used by the system to hold local variables and your position in the code when making multiple levels of calls in a program. Variables defined in the current module are at the top of the stack, then when you exit the module you “pop” that off, discarding it, so you have access to the calling function’s local variables again.

As such, creating your own stack data structure is often useful for “flattening” recursive functions. Rather than calling itself, the function can push onto its stack whatever state information it needs to retain, go do the other thing, then pop its previous data off the stack and resume. This lets you bypass the limitations of your execution environment’s stack (for maximum depth of function calls, for instance), and can be more efficient.

Specification

The Stack class I present here has these properties and methods:

  • Size As Long: the number of entries in the stack.
  • IsEmpty As Boolean: True if the stack contains no entries (same as stack.Size = 0).
  • Sub Push(value): Add a value to the stack.
  • Function Pop As Variant: Remove the most recently added element and return its value. If the stack is empty, returns EMPTY.
  • Function Peek As Variant: Returns the value of the most recently added element, but without removing it from the stack. If the stack is empty, returns EMPTY.

Implementation notes

Like the Queue class in the previous post, this class uses a helper class, StackNode, to contain the value property of the element and a pointer to the next node, if any. This is an even simpler implementation because it only needs to keep track of one end of the list of elements — the most recent one. When an element is added, it becomes the new top element and the new element’s Next pointer points to the previous top element.

Diagram of links between stack objects

In the above example, the stack was populated with the elements A, B, and C. When the caller starts “popping” those values, they’re returned in reverse order.

In the Queue class, to make it more versatile for handling objects versus whatever, there’s a way to specify what value to return if the queue is empty. Arbitrarily, I didn’t do that for Stack. Pop always returns EMPTY if the stack has nothing in it. This is inconvenient if the stack contains objects because you don’t know whether to use Set on the assignment. Just don’t be lazy — test IsEmpty before popping.

You could also create a child class that only supports objects, leveraging most of this code, and have it return Nothing if the stack is empty — that way a Set statement always works with Pop. I’ll discuss class inheritance and method overriding in a later post.

Book covers.

If you find this website helpful and want to give back, may I suggest buying, reading, and reviewing one of my excellent books? More are coming soon!

If you want email whenever there’s a new post, you can subscribe to the email list, which is 100% private and used only to send you information about stuff on this site.

Source

Private Class StackNode
	Public value As Variant
	Public Next As StackNode
	Sub New(value)
		If IsObject(value) Then Set me.value = value Else me.value = value
	End Sub
End Class

%REM
	Class Stack
	By Andre Guirard
	Description: Implementation of the classic Stack data structure.
	Constructor: New Stack
%END REM
Class Stack
	Private z_top As StackNode
	Private z_count As Long
	
	%REM
		Property Get Size
		Description: Number of elements in the stack
	%END REM
	Public Property Get Size As Long
		Size = z_count
	End Property

	%REM
		Property Get IsEmpty
		Description: TRUE if there are no elements in the stack.
	%END REM
	Public Property Get IsEmpty As Boolean
		me.IsEmpty = (z_top Is Nothing)
	End Property
	%REM
		Sub Push
		Description: Add a value to top of stack
	%END REM
	Sub Push(value)
		Dim nod As New StackNode(value)
		Set nod.Next = z_top
		Set z_top = nod
		z_count = z_count + 1 
	End Sub
	
	%REM
		Function Pop
		Description: Remove top value from stack and return it.
	%END REM
	Function Pop As Variant
		Dim nod As StackNode
		If z_count Then
			z_count = z_count - 1
			If IsObject(z_top.value) Then Set Pop = z_top.value Else Pop = z_top.value
			Set nod = z_top
			Set z_top = z_top.next
			Delete nod
		End If
	End Function
	
	%REM
		Function Peek
		Description: Return top value from stack without removing it.
	%END REM
	Function Peek As Variant
		If z_count Then
			If IsObject(z_top.value) Then Set Peek = z_top.value Else Peek = z_top.value
		End If
	End Function
	
	Sub Delete
		Dim nod As StackNode
		Do Until z_top Is Nothing
			Set nod = z_top.Next
			Delete z_top
			Set z_top = nod
		Loop	
	End Sub
End Class

Example code

FileSearch revised

For starters, I’d like to revisit the FileSearch example from the Queue class. You may recall there was a problem with it as regards the order in which it scanned the folders. The ordering imposed by the Queue class made the scan breadth-first rather than depth-first — the siblings of a folder were scanned before its descendants. This makes the list of resulting paths come out in an order most people would find undesirable.

If you instead use a Stack object to track your work, this lends itself well to a depth-first traversal of the folder tree. Anything you add to the stack is at the top, so it’ll be the next thing you pull off. So as you process a folder and “push” its subfolders, the subfolders will be processed next, as we desire.

sample folder structure

There is still one little problem though. Dir$ gives us folder contents in alphabetical order, and if we push them all onto the work stack in that order, when we pull them off they’ll be reversed. The matches in a folder will still be immediately followed by matches in its subfolders, but the list will not be alphabetical — matches in /foods/vegetables will appear before matches in /foods/meat.

You might decide this is acceptable, and for many functions it doesn’t really matter what order the work gets done so long as it all gets done. But in this case let’s make it nice — let’s have the output in an order a human would like best.

To make it work, the updated FileSearch function doesn’t put subfolder names directly onto the work stack when it sees them. Instead it pushes them onto a second stack, reverser. After it’s done scanning the folder, it shifts the entries from reverser onto the work stack. As its name suggests, the purpose of reverser is to reverse the order of the subfolder names so the last one added to work is the first in alphabetical order.

%REM
	Function FileSearch
	Description: Recursively search a given folder for files who name match a set of patterns. Works on any OS.
	Arguments:
		baseDir: the folder you want to search. May optionally have a directory delimiter at the end, e.g. "C:\foo" or "C:\foo\".
			Should work on any OS.
		patterns: a string delimited with "|" listing the second operands for a LIKE operator to match against the filename, case insensitive.
			E.g. "*.dat|ac[0-5]*.cab" to find all ".DAT" files and all ".CAB" files whose names begin "ac" followed by digit 0 to 5.
	Returns: array of strings containing full filepaths of all matching files, or if no matches found, array containing one blank element.
%END REM
Function FileSearch(ByVal baseDir$, ByVal patterns$) As Variant
	Dim delim$, afile$, results$, adir$, patternarr
	Dim work As New Stack
	Dim reverser As New Stack ' used to reverse a list of strings
	Dim files List As Boolean
	
	' find what path delimiter the caller prefers.
	If basedir Like "/*" Then
		delim = "/" ' linux style path
	ElseIf basedir Like "\*" Or basedir Like "[A-Z]:\*" Then
		delim = "\" ' windows style path
	Else
		Error 20030, "FileSearch needs an absolute filepath."
	End If
	
	patternarr = Split(LCase(patterns), "|")
	' internally we want all filepaths to end with the folder separator
	If baseDir Like "*[!\/]" Then baseDir = baseDir & delim
	work.push basedir
	
	' while there are directories in the queue, process the first directory.
	Do Until work.IsEmpty
		adir = work.Pop
		Erase files
		afile = Dir$(aDir & "*", Attr_hidden + Attr_system) ' all files but not directories.
		Do Until afile = ""
			files(afile) = True ' remember the names of the files
			If IsLikeAnyOf(LCase(aFile), patternarr) Then
				results = results & NEWLINE & adir & afile
			End If
			afile = Dir$
		Loop
		
		' now find the names of each subfolder.
		afile = Dir$(aDir, Attr_directory + attr_hidden) ' this will include files also
		Do Until afile = ""
			If afile <> "." And afile <> ".." Then
				If Not IsElement(files(afile)) Then
					' this is a folder. Save its name to add to the work list.
					Call reverser.Push(afile)
				End If
			End If
			afile = Dir$
		Loop
		' if any subfolders were found, queue them for processing next.
		Do Until reverser.IsEmpty
			Call work.Push(aDir & reverser.Pop & delim)
		Loop
	Loop
	FileSearch = Split(Mid$(results, 2), NEWLINE)
End Function

Obviously a stack object isn’t the only way to process a list of strings in reverse order, but since this is a demo I thought it’d be cool to do it that way.

Postfix expression evaluator

An arithmetic expression in postfix notation has the operands preceding the operator, e.g. “2 3 +” rather than “2 + 3”. An advantage of postfix is that it doesn’t require parenthesis to group things — the operator order is implicit in the order in which you arrange the terms. For instance, “4 2 3 + /” means first add 2 and 3, then divide 4 by that (= 0.8).

A stack is ideal for evaluating such an expression, because you have to accumulate operands until you find out what to do with them, and when you hit the operator, it operates on the most recently encountered two operands, and the result goes back on the stack to be available for further operations. For instance, to evaluate “1 2 3 + + 8 5 – /”, here’s how the stack looks as we process each token:

TokenEvaluation stack
11
22 1
33 2 1
+5 1
+6
88 6
55 8 6
3 6
/2

The code to do this evaluation is surprisingly simple.

%REM
	Function PostfixEval
	Description: Evaluate a mathematical expression in postfix syntax. Tokens are delimited by spaces, e.g. "2 3 +"
%END REM
Function PostfixEval(expr$) As Double
	Dim operands As New Stack
	Dim op1 As Double, op2 As Double, result As Double
	Dim tokenarr
	tokenarr = FullTrim(Split(expr, " "))
	ForAll token In tokenarr
		If IsNumeric(token) Then
			operands.Push CDbl(token)
		ElseIf token Like "[-+*/]" And operands.Size > 1 Then
			' an operator, and there are enough operands to evaluate it
			op2 = operands.Pop
			op1 = operands.Pop
			Select Case token
			Case "+"
				result = op1 + op2
			Case "-"
				result = op1 - op2
			Case "*"
				result = op1 * op2
			Case "/"
				result = op1 / op2
			End Select
			operands.Push result
		Else
			Error 20030, "Invalid postfix syntax"
		End If
	End ForAll
	If operands.Size <> 1 Then ' not enough operators to use up all the operands.
		Error 20030, "Invalid postfix syntax"
	End If
	PostfixEval = operands.Pop
End Function

Final thoughts

There are other ways to implement the functionality of a stack besides that given here. Behind the scenes it could use an array instead of a singly linked list (if you don’t mind the size limit). It could leverage the LinkedList class I plan to discuss in a future post, which is ridiculously overqualified for the purpose of managing a stack but you don’t have to use all its functions.

This is just the minimal general-purpose implementation that doesn’t rely on other classes.

Appendix 1: The Mikle Stack

A comment below about an alternate implementation led me to try out that option. Here’s the code for a stack implementation with the same methods, but based on a List data structure rather than a linked list.

%REM
	Class MikleStack
	By Mikle
	Description: Implementation of the classic Stack data structure using a List
	Constructor: New MikleStack
%END REM
Class MikleStack
	Private z_stak List As Variant
	Private z_count As Long
	
	%REM
		Property Get Size
		Description: Number of elements in the stack
	%END REM
	Public Property Get Size As Long
		Size = z_count
	End Property

	%REM
		Property IsEmpty (read-only)
		Description: TRUE if there are no elements in the stack.
	%END REM
	Public Property Get IsEmpty As Boolean
		me.IsEmpty = (z_count = 0)
	End Property
	
	%REM
		Sub Push
		Description: Add a value to top of stack
	%END REM
	Sub Push(value)
		z_count = z_count + 1
		If IsObject(value) Then Set z_stak(z_count) = value Else z_stak(z_count) = value
	End Sub
	
	%REM
		Function Pop
		Description: Remove top value from stack and return it.
	%END REM
	Function Pop As Variant
		If z_count Then
			Dim key$
			key$ = z_count 
			If IsObject(z_stak(key)) Then Set Pop = z_stak(key) Else Pop = z_stak(key)
			Erase z_stak(key) ' so if it's an object there's not an unnecessary reference to it from here.
			z_count = z_count - 1
		End If
	End Function
	
	%REM
		Function Peek
		Description: Return top value from stack without removing it.
	%END REM
	Function Peek As Variant
		If z_count Then
			Dim key$
			key$ = z_count 
			If IsObject(z_stak(key)) Then Set Peek = z_stak(key) Else Peek = z_stak(key)
			z_count = z_count - 1
		End If
	End Function	
End Class

4 thoughts on “LotusScript Stack data structure”

  1. stack class :
    Why not this :

    Function Pop As Variant
    Dim nod As StackNode

    Set nod = z_top
    Set z_top = nod.Next
    Delete nod
    z_count = z_count – 1
    pop = me.peek()
    End Function

    Sub Delete
    Do Until z_top Is Nothing
    me.pop
    Loop
    End Sub

    1. I prefer to have Pop return EMPTY rather than throw an “Object variable not set” error when the stack is empty.

      It’s true the Delete routine is more complicated than it needs to be — I wanted the better performance of not having to return all the values.

      However, given that there are no reference loops in this data structure, we could also just not have a Delete sub. Regular garbage collection should handle this case.

  2. The implementation is classic from the tutorial, but it looks too complicated for lotusScript. How about a class that has a list and a counter? List is а very powerful tool, this stuff is stronger than Goethe’s “Faust”!
    something like this:
    class stackSimple
    private counter as long, values list of variant
    sub push(newVal)
    values(counter++) = newVal
    end sub
    function pop
    if( counter )then pop= values(counter) else pop= empty:exit function
    erase values(counter)
    counter–
    end function

  3. I did the Mikle version with the same checks and comments and it’s 8 lines shorter, so that’s good. Of course then it’s not the linked-list example I wanted. 🙂
    I’ll include the shorter version above also. Thanks Mikle!

Leave a Reply

Your email address will not be published. Required fields are marked *