class %Compiler.Util.Flo
extends %Library.RegisteredObject
FLO
This class can be instantiated to produce an object that is useful for code
generation. The object contains a graph (the flo graph) where the root node of
the graph represents a code generation 'task'. Each node has code a code attribute
and a block type attribute. The block type can be 0 (none), 1 (conditional block),
or 2 (loop). Conditional and Loop nodes also have a block terminator to support
various styles of looping (do...while, while...., for... etc).
Flo also maintains a node stack and a symbolStack. The top of the node stack is
always the current node and any code lines generated are placed in the current
node unless otherwise specified. This allows the code generator to optionally
maintain "bookmarks" so that code can be generated 'out of order'. Each node in
a flo may also own symbol tables. Some code generators need to push a new symbol
table onto a stack, for example, nested procedure calls might require that a new
symbol table be created. The stack of symbol tables (symbolStack) is just a stack
of nodes that need new a new symbol 'scope'. Symbols defined at higher stack levels
are always visible to lower levels (higher in the symbol stack) but higher nodes
or sibling nodes cannot see the symbols in siblings or lower nodes.
Code generation is handled by calls to the queueCode method. Each line of code
to be generated is passed to queueCode and it is placed at the end of the node's
code attribute. If the node is specified (bookmarks) then the code is placed in
that node, otherwise the current node (top of the nodeStack) is used. When flo is
asked to dequeue the code value the first thing that is done is to 'compile' the
symbol tables. Each symbol may contain references to other symbols. Each symbol table
is processed using a topological sort to resolve nested symbol references. Then
the flo graph is traversed using bfs traversal and the code attribute from each
node is output. Each code line is scanned and any symbol references are resolved
before it is output. The sequence in which the code is output is such that the code
attribute of the current node is always output prior to that of its children. That
means that bookmarked code generation will always preceed the code in the child nodes.
When this is not desired it is necessary to branch the graph. 'Branching the graph'
pops two nodes from the node stack, creates a new node that is a sibling of the second
node popped and pushes that sibling onto the node stack. The effect is that code generated
to the current node will then be placed in a sibling and will occur in the output code
after the children of the original node. This branching occurs automatically when the
node stack is popped and the new current node (top of the nodeStack) already has
children.
###; TODO: an undesirable side effect of this automatic branching is that
the new sibling node cannot see the symbol tables in the original node.
that is only a problem if that node is a 'new scope' node and is currently
on the symbolStack. For now the problem is handled by automatically creating
a new child node of every 'new scope' node. That child is only popped when
the end of the new scope is reached and then two nodes are popped so that any
automatic sibling branching occurs above the new scope node.
The use of symbols by the code generator can greatly reduce the complexity of the
generator but care should be used. Symbols should be used when the definition of the
symbol is not known to all places where the symbol is referenced. For example, if a
code generator needs to reference some object whose implementation is not know then
define a symbol for the object along with the implementation. All other references to
the object in generated code simply reference the symbol.
property codeContextStack
as %Integer;
property currentNode
as %Integer;
property defaultContext
as %String [ InitialExpression = "p" ];
property errorQueue
as %Integer;
property nodeStack
as %Integer;
property queuedSymbolQ
as %Integer;
property symbolStack
as %Integer;
The symbolStack is a stack of nodes for symbol scoping.
BEGIN establishes a new symbol scope and every END pops to the previous scope. Users of FLO
need to push/pop the symbolStack when needed by providing an indicator when pushing a new node
onto the nodeStack.
property topNode
as %Integer;
method DequeueFlo(intNode, strTabs, code, procBlock)
method ExtractSymbolDependency(referenceContext, expr, ByRef dependency)
as %Status
method LookupSymbol(ByRef intNode As %Integer, strContext As %String, strSymbol, ByRef found, ByRef resolveOperator)
as %CacheString
method ResolveCodeLine(expr, sourceLocation, intNode, strContext, ignoreSymbolError, resolved)
as %Status
ResolveCodeLine() parses source for symbol references. Any references are replaced the the definition
from the compiled symbol table. The compiled symbol table for the requested context is search and, if
found, the symbol value replaces the symbol reference in the source line. If the symbol cannot be found
then the symbol reference is replaced with an {UNRESOLVED:} message. There is also a
resolveOperator that controls how the replacement proceeds. If the resolveOperator is "C" then the
resolved symbol value is spliced into the source line as a constant. Otherwise, the symbol is literally replaced
with the symbol value.
method addHostVariable(hostVar As %CacheString = "", symbol As %String = "", symbolContext As %String = "", node As %Integer = "", type As %CacheString = "", ByRef symbolFound As %Integer)
as %String
addHostVariable()
This method adds a new host variable to a symbol context at a given node. If the symbol that this host variable is 'bound' to
already exists then nothing is done and symbolFound is returned as true. Otherwise, the host variable is added and bound to the
requested symbol. If the host variable name supplied is not unique then it is mangled until a unique name is found. If no hostVar
is specified then a default hostVar root name of zzcv is used.
method addNode(parent As %Integer, blockType As %Integer = 0, blockEnd As %CacheString = "", pNodeType As %String = "%node")
as %Integer
addNode()
This method adds a new node to the floGraph. Normally, pushNode is the only caller of this method
but some code generators might want to exert some control over the graph structure. Calling this method
directly places the responsibility for the code sequence in the hands of the caller.
method addSymbol(node As %Integer = "", symbolContext As %String = "", symbol As %String, expression As %CacheString, type As %CacheString = "", resolveOperator As %String = "")
as %Integer
addSymbol() Non-recursive function to add a symbol if not already defined
method branchNode(nodeCount As %Integer = 1, blockType As %Integer = 0)
as %Integer
branchNode()
Pop nodeCount nodes off the node stack. Push a new node that a child of the
resulting top node onto the stack. Make it a block if blockType is TRUE.
method compileSymbols(intNode)
classmethod getPublicSymbolType(symbolContext As %String, symbolName As %CacheString)
as %CacheString
method getSymbolType(symbolContext As %String, symbolName As %CacheString, symbolNode As %Integer)
as %CacheString
classmethod lookupPublicSymbol(strContext, symbolName As %CacheString, found, ByRef resolveOperator)
as %CacheString
method nodeInLoop(node As %Integer = "")
as %Boolean
nodeInLoop() returns TRUE if the current node is subordinate to a LOOP block type.
method popCodeContext()
as %Integer
popCodeContext() restores the currentNode pointer to the node at the top of the ..codeContextStack stack.
method popNodes(nodeCount As %Integer = 1)
as %Integer
popNodes()
Pop nodeCount nodes off the node stack. If the new top node has children then add
another child to that node, pop it from the stack and push the new child onto the stack.
method popSymbolStack(nodeCount As %Integer = 1)
as %Integer
popSymbolStack()
Pop the symbol stack. This is typically done when the end of the current symbol scope is reached.
Note that problems can occur when branching occurs at odd times - either automatic because of sibling
nodes and code sequencing conflicts or with manual branching.
method pushCodeContext(pCodeNode As %Integer = 0)
as %Integer
pushCodeContext() is an interesting method. Think of a code as a stream that is flowing into a single
node in Flo. All node switching methods are about parent of the current node or children of the current node.
What pushCodeContext() does is to redirect the code stream into a Flo node that might not have any connection
to the currentNode. The currentNode is pushed onto the codeContext stack so that popCodeContext can restore the
code stream to the prior codeContextStack node.
method pushNode(blockType As %Integer, blockEnd As %CacheString = "", pushSymbolStack As %Integer = 0, pNodeType As %String = "%node")
as %Integer
pushNode() creates a new node in the Flo that is a child of the currentNode. The newly pushed node becomes the currentNode.
if pushSymbolStack is TRUE then the symbol stack is also pushed so the symbols defined at this node level
are not visible to Flo nodes higher in the tree.
method pushSymbolStack(tNode As %Integer = 0)
as %Integer
pushSymbolStack()
Push the symbol stack.
method queueCode(code As %CacheString, sourceLocation As %CacheString = "")
queueCode()
This method adds a code line to the current floNode
method queueCodeArray(ByRef code As %CacheString, sourceLocation As %CacheString = "")
queueCodeArray()
This method adds a code line to the current floNode
method queueCodeToBookMark(bookMark As %Integer, code As %CacheString, sourceLocation As %CacheString = "")
queueCodeToBookMark()
This method adds a code line to a book marked node. Note that code queued
to a node that has children will appear in the dequeued code stream before any
code dequeued from those children.
method queueSymbolRequest(symbolContext As %String = "", symbolName As %String)
queueSymbolRequest()
Place a symbol in the deferred symbol Queue for later definition. The symbol is actually
copied from some other place (higher in the graph) to the requestNode. The idea is that the
higher definition is unresolvable, unreachable or maybe it would resolve to a different expression
if not defined in the requestNode.
method setSymbolExpression(node As %Integer = "", symbolContext As %String = "", symbol As %String, expression As %CacheString)
as %Boolean
setSymbolExpression() Non-recursive function to set a symbol's expression. Symbol must already be defined
classmethod strrep(str, replace, with)
as %CacheString
method visit(intNode, aov, stack, symbolId)
as %Status