Philippe Verdy
2018-11-04 15:44:52 UTC
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)
{ -- 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)