Define how document attributes are represented in the ASG
As part of defining a semantic representation of an AsciiDoc document, we must determine where document attributes fit within that structure. It's not enough for document attributes to reside as state in the parser since they can impact how nodes are interpreted after parsing is complete. In other words, the document attributes have to be tracked and represented in the ASG.
To start, we first need to establish that a document attribute is more than just a map entry (name/value pair). Where and how the attribute was declared (set or unset) matters too. Thus, we need to store document attributes as a value record. That record has the following properties:
- value - the value of the attribute if set, or
null
if explicitly unset - source - records how the attribute was set; an enum that is one of the following values: external, header, body, intrinsic
- writable - whether the attribute can be changed or unset (i.e., mutable)
Thus, a document attribute entry consists of a name and value record. These entries are stored in an insertion-ordered map, where the map keys are the attribute names.
Let's assume the document header contains the following attribute entry:
:toc: left
Here's an example of the resulting entry in the document attributes map:
{
toc: {
value: 'left',
source: 'header',
writable: true,
}
}
With the information provided by the value records, it's possible to find document attributes that are immutable (i.e., locked), document attributes that have been explicitly unset, document attributes that are implicit, document attributes set from the API or CLI (and not later overridden), etc. This is all information that would not have been available if the document attributes were stored with their value and only when set.
The document attributes stored on the document node in the ASG should be stored using this value record. That map should be the compiled collection of document attributes up to the end of the document header (thus discarding any intermediate state).
AsciiDoc permits attributes to be set or unset using attribute entries in the document (in the header or in the body). Not all document attributes are declared this way. However, for those that are, those entries should be tracked in the ASG. Those records will need to be stored differently. That brings us to attribute entry nodes.
An attribute entry is recorded in an insertion-ordered map (or should it be an array?) for each group of attribute entries. In this case, the value of the entry can be the attribute value if set, or null
if unset. An attribute declared using an attribute entry is always writable and the source is implicit, so there's no reason for using a value record in this case.
For attributes declared in the header, these attribute entries are stored on the attributeEntries
property on the value of the header
property on the document. For attributes declared in the body, these attribute entries are either stored on the attributeEntries
property on the ensuing block, or stored as a sibling node of that block. (Prototyping is required in order to resolve this choice).
While it's true that all attributes declared in the header will already be represented in the map of attributes on the document, it's important to track them to ensure the document was correctly parsed. (As an alternative, the document attributes map could be all attributes set before the header, then the header attributes could be replayed onto the proxy, just like attributes declared in the body).
Within any attribute entries map, only the last entry for a given attribute name in that scope should be stored (i.e., locally impactful). Hence, if the attribute is set, then unset, no entry is stored. If the attribute is set, then set again, the second value is used. If the attribute is unset, then set, the set value of the attribute is stored. If the attribute is not mutable, the entry is discarded (since the attribute entry is effectively nullified).
As the body of the document is parsed, the effective document attributes need to be tracked. However, the map of document attributes as it exists at the end of the header should not be modified. Instead, that object should be proxied. Any updates to the map from attribute entries in the body should be applied to the proxy during parsing. That way, attribute entries in the body don't impact the state of the document attributes bound to the document. Yet the parser will still have access to the updated map as parsing proceeds.
If a node is accessed at random from the parsed document (ASG), the view of the document attributes needs to be built on demand by proxying the document attributes map on the document and replaying all the attribute entries in the body up to the start of the node. These proxy objects could be cached to avoid repeated and redundant computations.