This is the documentation for the RELOADbasiC extension of FreeBASIC.

= Nodespecs =
A nodespec is an expression for a typed value of a "root" NodePtr variable or a child/descendent Node.

Descendant syntax:
  nodevar."childname"[."deeper descendants" ...][.attribute]...

Zero spec syntax:
  nodevar..attribute[.attribute]...

nodevar must be a Node ptr expression: either a local variable declared with DIM or as a function
argument, or an expression, possibly containing dots, like cont.root or .root declared with "declare
as nodeptr" (see below).  (This requirement should probably be loosened further.)

Zero spec nodespecs are just an alternative to calling GetInteger, etc.  They can't be used where a
nodespec is required by WITHNODE, READNODE or LOADARRAY.  They must have an attribute.

Nodespecs on a 'LoadArray' line can also contain an index, see the LoadArray section.


== Nodespec type attributes ==
.ptr            : NodePtr: A pointer to the node itself, not its value.
.name           : The NodeName
.integer        : (Default)
.int            : Alias
.float
.double         : Alias to float
.string
.str            : Alias
.zstring
.zstringsize    : Length of the zstring/binary blob
.bool           : Equal to GetInteger(node) <> 0, like GetChildNodeBool. Always either YES or NO.
				  So a Null node is NO, and it's possible to supply a default if the node doesn't
				  exist.
.exists			: More common usage: equal to YES if the node exists, NO otherwise, like
				  GetChildNodeExists. No default value allowed, of course.
				  Inside a ReadNode, lines/rules containing this nodespec are always executed.


== Other Nodespec attributes ==
.warn           : Print a warning message using warn_func if the node doesn't exist
.required       : Print an error message using error_func if the node doesn't exist, then exit
				  the sub or function (with return value 0)
.default(value) : A default value if the node does not exist (NOT a default value for 'Null' nodes).
				  The default value is evaluated lazily (only if required).
				  This also causes a line inside a ReadNode to always be processed, with the default
				  value, even if the node is missing. See below.
.ignore         : The resulting nodespec is a directive, not an expression. It may only appear
				  in a ReadNode, causing that node to be skipped without a warning.
				  No other attributes should be given.
.oob_error      : For LoadArray only. Print errors instead of warning messages and exit the sub
				  or function on out-of-bounds errors.


= Declare as NodePtr =

If you want a nodespec starting with an expression which isn't a simple variable because it contains
dots, such as cont.root."foo" or .root."foo", or otherwise isn't recognised e.g. a reference to
a UDT member inside a method, place a declaration in the same function:
  DECLARE AS NODEPTR expression [, expression]...

You can use "node ptr" instead of "nodeptr".
E.g.
  declare as Node ptr this.root, parent

= WithNode =
Syntax:
  WITHNODE nodespec AS nodevar
    ...
  END WITHNODE

WithNode is mostly just a shortcut for
  DIM nodevar as Node ptr = nodespec.ptr
(nodevar should not be manually declared, it's done automatically), however it has an important use
inside ReadNode block (more there).

Inside a WITHNODE nested inside a READNODE you can write CONTINUE READNODE and EXIT READNODE.

= ReadNode =
Syntax:
  READNODE nodespec AS nodevar [, IGNOREALL] [, DEFAULT]
    ...
  END READNODE
or
  READNODE nodevar [, IGNOREALL] [, DEFAULT]
    ...
  END READNODE

The first form is just a shortcut for
  DIM nodevar as Node ptr = nodespec.ptr
  READNODE nodevar [...]
(nodevar should not be manually declared, it's done automatically).

nodevar must be a NodePtr. The body contains lines of arbitrary source which contain exactly one
nodespec, referring to one of the children of nodevar. The children of nodevar are iterated over,
and for each the matching line in the body is executed. If none match, then a warning is printed,
unless "ignoreall" appears in the header. A child can also be ignored with an .ignore directive.
If a line matches multiple times then it is run each time. If line doesn't match at all, then a
warning or error is printed if its nodespec has the .warn or .required attributes, but it's not
run with the default default value (ie. 0, 0.0 or "") unless either it has an explicit .default
attribute, or "default" appears in the ReadNode header, or it is type '.exists'.

"default" in the ReadNode header has no effect on LoadArray lines, since arrays are always flushed.

You can use EXIT READNODE (must be on the same line as a nodespec) to stop early.


Example:


 READNODE parent."state" as node
  node."misthought_feature".ignore

  gen(genDamageCap) = node."damage_cap"
  gen(genLevelCap) = small(node."level_cap".default(99), gen(genMaxLevel))

  gen(genTextboxBackdrop) = node."textbox"."backdrop"
 END READNODE

If node."damage_cap" is missing, then gen(genDamageCap) is uninitialised, while gen(genLevelCap)
defaults to 99. You can't add lines like
  txt.id = node."textbox"."id"
  gen(genTextboxBackdrop) = tb."textbox"."backdrop"
to this block, instead you would have to add
  WITHNODE node."textbox" as tb
   txt.id = tb."id"
   gen(genTextboxBackdrop) = tb."backdrop"
  END WITHNODE


= LoadArray =
Syntax:
 (inside a READNODE block)
 LOADARRAY array_name($varname) = <expression containing nodespec indexed by varname>

Syntax for indexed nodespec (can't be zero spec):
where nodespec[$varname].continued_nodespec
  nodevar."childname" `[$'  varname `]' [."descendants" ...] [.attribute ...]
  eg.
   node."foo"[$var]."bar".double

LoadArray can only occur inside a ReadNode block. It allows mapping multiple children of the
ReadNode parent Node to elements of a (predetermined length) array. The integer value of each
child is used as an array index, and (normally) one of its descendents is used as the evaluated
value of the nodespec.
The array is always zeroed (or using provided default) out at the start of the READNODE block,
using the full expression with the default value substituted for the nodespec! So if the
expression calls a function, that function could be called twice per array index (or more,
if there are duplicates). If way of flushing the array is undesirable, it could be changed.

You can use the index variable elsewhere in the expression. You have to declare it yourself,
(as an integer)!

As with other ReadNode lines, if the same node occurs multiple times, it'll be assigned to the
array multiple times.

Note that there is no difference between a certain index value in the range of the array
being missing, and the requested descendant being missing. For example, in
		loadarray buf($i) = nod1."key"[$i]."child".default(-1)
if buf is (0 TO 99), nod1."key"[10] missing is indistinguishable from
nod1."key"[10]."child" begin missing... except that in the later case, the expression is
evaluated twice.

If the nodespec has the .oob_error attribute, then an error instead of a warning is thrown if
the index is out of the array bounds. .required and .warn aren't allowed.

Examples:
 DIM i as integer
 ...
 LOADARRAY npc.extra($i) = n."extra"[$i]."int"
 LOADARRAY bits($i) = n."bit"[$i].exists
 LOADARRAY dat($i) = extract_data(i, n."data"[$i]."value")


= Directives =
Syntax:
  #warn_func = <FB SUB name>
  #error_func = <FB SUB name>

Set the functions called on warnings and errors. Defaults to debug. Takes a single argument,
a string.

If placed outside of a file, affects the rest of the file. If inside a function, remains in
effect for the rest of the function. Can't be placed inside ReadNode, WithNode.

= TODO =

* Finish writing this file
* Clean up the source: handling of output and indent is horrible. Also variable naming confusion between strings, ASTNodes, others (NodeSpecs)
* Assigning to nodespecs, other writing features
* .once
* .longint
* nodespecs rooted by Doc ptrs
* Global name indexing instead of RB_SIGNATURE hack
* More care with nameindex tables (failures on NULL nodes)
* Improve the tests
* Using nodespecs on an ELSEIF line is not supported
