I propose to extend te syntax of table constructors like
this:
{
--
starts a new local context (closure)
[:
ref0 = 'k1'] = : ref1 = v1, --
['k1'] = v1 and set external ref0 = 'k1'
[:
ref2] =
v2, -- [1] =
v2 and set local ref2 = 1
[:local ref3]
=
v3, -- [2] = v3
and set local ref3 = 2
[:local ref4 = 'k2'] =
:local ref5 = v4, -- ['k2'] = v4 and set
local ref4 = 'k2', local ref5 =
v4
[:local ref6 = 'k3'] =
: ref7 = v5, -- ['k3'] = v5
and set local ref6 = 'k2', external ref7 =
v5
k4 =
ref0, -- ['k4'] =
'k1'
[
'k5'] =
ref2, -- ['k5'] = v2
ref1, -- [3]
= v1
}
--
close the local context
The idea is to allow constructors to use cyclic references by
allowing to define "tracking" variables keeping the
key (eventually the implicit numeric key) and the value
assigned to a key; these variables can be defined in local scope
(the scope is a closure, the local context of the table constructor
itself) or in external scope (any enclosing scope, eventually
outside any table constructor, or in another enclosing table
constructor).
The syntax for keys is not changed, it still uses the [] to
enclose an _expression_.
However the _expression_ allowed in a key (between []) or in a
value (after the = if there's a key specified before) is extented
by allowing one of these form to set variables:
:local variable, (only for key expressions), or
:local variable = _expression_, or
:variable, (only for key expressions), or
:variable = _expression_, or
_expression_, or
* The first two forms define the variable in the current
local scope (it creates a new variable in the local context, like
with function closures, or local contexts of enuneration variables
in "for", hiding any external variable defined with the same
name).
* The last two forms can use any lvalue _expression_: if the
external context does not have this variable defined, it will
create the variable in the external context, otherwise it will
replace its current value.
This syntax extension is simple to parse (no complex
look-ahead or backtracking), it just starts with a leading ":"
before the local or external variable, the '=' sign (optional if
there's no _expression_ and permitted only in the key part, required
in the value part) and the _expression_ (optional only in the key
part because it implies an implicit numeric key)
You may prefer to change the syntax distinguishing the
assignment of local and external variables (":local variable"
versus ":variable"). For example:
- ":variable" for the a local variable (must be a "name" or
"[_expression_]"), or
- "?variable" for setting an external variable (which can be
any lvalue _expression_)
In all cases, the locality is limited to the scope of the
table constructor, it is permitted to reassign the same variable in
the same local or external scope, like in standard Lua assignment
instructions, or in standard declarations of local variables in
functional blocks.
No limit is set on the types of values that these variables can
hold (only the nil value is invalid as the key of a table entry,
but not invalid as the value of a table entry).
This allows to entirely serialize any table (including
cyclic/recursive ones) in pure data-only format (the data-only
format is independant of the context where it is used if all
variables set inside the main table constructor are defined locally
in the main table constructor (but external, non-local, variables
can still be used in a sub-table).
It is even possible to serialize tables containing code (i.e.
function blocks), with their defined lamda expressions, possibly
even coroutines (only functions that are defined as external
C-functions cannot be serialized with their code, they can only be
referenced by their binding name; C functions and coroutines are
most probably excluded from pure data-only table constructors, but
lambda functions may be permitted if the code inside them does not
any use external references to variables not in local scope of the
main serialized table and does not use external C-functions or some
restricted functions of the Lua library; some functions of the Lua
library may still be allowed, including pcall(), that can locally
handle exceptions)