mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Decorator system simplification, removed paragraph decorators, added function decorators
This commit is contained in:
parent
6f564ea0e2
commit
0171d92352
16 changed files with 265 additions and 290 deletions
68
README.md
68
README.md
|
|
@ -3,7 +3,7 @@ Anselme
|
||||||
|
|
||||||
The overengineered dialog scripting system in pure Lua.
|
The overengineered dialog scripting system in pure Lua.
|
||||||
|
|
||||||
**Has been rewritten recently, doc is still WIP**
|
**Has been rewritten recently, doc and language are still WIP**
|
||||||
|
|
||||||
Purpose
|
Purpose
|
||||||
-------
|
-------
|
||||||
|
|
@ -90,7 +90,7 @@ Another line.
|
||||||
|
|
||||||
When executing a piece of Anselme code, it will not directly modify the global state (i.e. the values of variables used by every script), but only locally, in this execution.
|
When executing a piece of Anselme code, it will not directly modify the global state (i.e. the values of variables used by every script), but only locally, in this execution.
|
||||||
|
|
||||||
Right after reaching a checkpoint (line or decorator), Anselme will merge the local state with the global one, i.e., make every change accessible to other scripts.
|
Right after reaching a checkpoint line, Anselme will merge the local state with the global one, i.e., make every change accessible to other scripts.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ main
|
$ main
|
||||||
|
|
@ -296,19 +296,69 @@ And this is more text, in a different event.
|
||||||
|
|
||||||
### Line decorators
|
### Line decorators
|
||||||
|
|
||||||
Every line can also be followed with decorators, which are appended at the end of the line and affect its behaviour.
|
Every line can also be followed with decorators, which are appended at the end of the line and affect its behaviour. Decorators are just syntaxic sugar to make some common operations simpler to write.
|
||||||
|
|
||||||
* `~`: condition decorator. Same as an condition line, behaving as if this line was it sole child. Typically used to conditionally execute line. Does not affect following else-conditions.
|
* `~`: condition decorator. Same as an condition line, behaving as if this line was it sole child. Typically used to conditionally execute line.
|
||||||
|
|
||||||
* `§`: checkpoint decorator. Same as a checkpoint line, behaving as if this line was it sole child.
|
|
||||||
|
|
||||||
* `#`: tag decorator. Same as a tag line, behaving as if this line was it sole child.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ fn
|
$ fn
|
||||||
run this line only once ~ 👁️
|
run this line only once ~ 👁️
|
||||||
```
|
```
|
||||||
|
|
||||||
|
is equivalent to:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fn
|
||||||
|
~ 👁️
|
||||||
|
run this line only once
|
||||||
|
```
|
||||||
|
|
||||||
|
* `#`: tag decorator. Same as a tag line, behaving as if this line was it sole child.
|
||||||
|
|
||||||
|
```
|
||||||
|
tagged # 42
|
||||||
|
```
|
||||||
|
|
||||||
|
is equivalent to:
|
||||||
|
|
||||||
|
```
|
||||||
|
# 42
|
||||||
|
tagged
|
||||||
|
```
|
||||||
|
|
||||||
|
* `$`: function decorator. Same as a function line, behaving as if this line was it sole child, but also run the function.
|
||||||
|
|
||||||
|
```
|
||||||
|
text $ f
|
||||||
|
```
|
||||||
|
|
||||||
|
is equivalent to:
|
||||||
|
|
||||||
|
```
|
||||||
|
~ f
|
||||||
|
$ f
|
||||||
|
text
|
||||||
|
```
|
||||||
|
|
||||||
|
This is typically used for immediatletly running functions when defining them, for example for a looping choice :
|
||||||
|
|
||||||
|
```
|
||||||
|
~$ loop
|
||||||
|
> Loop
|
||||||
|
@loop
|
||||||
|
> Exit
|
||||||
|
```
|
||||||
|
|
||||||
|
is equivalent to (since empty condition is assumed true):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ loop
|
||||||
|
> Loop
|
||||||
|
@loop
|
||||||
|
> Exit
|
||||||
|
~ loop
|
||||||
|
```
|
||||||
|
|
||||||
### Text interpolation
|
### Text interpolation
|
||||||
|
|
||||||
Text and choice lines allow for arbitrary text. Expression can be evaluated and inserted into the text as the line is executed by enclosing the [expression](#expressions) into brackets.
|
Text and choice lines allow for arbitrary text. Expression can be evaluated and inserted into the text as the line is executed by enclosing the [expression](#expressions) into brackets.
|
||||||
|
|
@ -368,7 +418,7 @@ Valid identifiers must be at least 1 caracters long and can contain anything exc
|
||||||
|
|
||||||
When defining an identifier (using a function, checkpoint or variable delcaration line), it will be defined into the current namespace (defined by the parent function/checkpoint). When evaluating an expression, Anselme will look for variables into the current line's namespace, then go up a level if it isn't found, and so on.
|
When defining an identifier (using a function, checkpoint or variable delcaration line), it will be defined into the current namespace (defined by the parent function/checkpoint). When evaluating an expression, Anselme will look for variables into the current line's namespace, then go up a level if it isn't found, and so on.
|
||||||
|
|
||||||
In practise, this means you have to use the "genealogy" of the variable to refer to it from a line not in it indentation block:
|
In practise, this means you have to use the "genealogy" of the variable to refer to it from a line not in the same namespace:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ fn1
|
$ fn1
|
||||||
|
|
|
||||||
|
|
@ -99,18 +99,9 @@ local function eval(state, exp)
|
||||||
end
|
end
|
||||||
-- anselme function
|
-- anselme function
|
||||||
if type(fn.value) == "table" then
|
if type(fn.value) == "table" then
|
||||||
-- checkpoint & checkpoint decorator
|
-- checkpoint
|
||||||
if fn.value.type == "checkpoint" or fn.value.checkpoint then
|
if fn.value.type == "checkpoint" then
|
||||||
local r, e
|
local r, e = run(state, fn.value.child, not exp.explicit_call)
|
||||||
if fn.value.type == "checkpoint" then
|
|
||||||
r, e = run(state, fn.value.child, not exp.explicit_call)
|
|
||||||
-- checkpoint decorators: run single line or resume from it.
|
|
||||||
-- checkpoint & seen variables will be updated from the interpreter usual checkpoint-reaching code.
|
|
||||||
elseif exp.explicit_call then
|
|
||||||
r, e = run(state, fn.value.parent_block, false, fn.value.parent_position, fn.value.parent_position)
|
|
||||||
else
|
|
||||||
r, e = run(state, fn.value.parent_block, true, fn.value.parent_position)
|
|
||||||
end
|
|
||||||
if not r then return r, e end
|
if not r then return r, e end
|
||||||
return r
|
return r
|
||||||
-- function
|
-- function
|
||||||
|
|
|
||||||
|
|
@ -57,29 +57,25 @@ local run_block
|
||||||
local function run_line(state, line)
|
local function run_line(state, line)
|
||||||
-- store line
|
-- store line
|
||||||
state.interpreter.running_line = line
|
state.interpreter.running_line = line
|
||||||
-- condition decorator
|
-- if line intend to push an event, flush buffer it it's a different event
|
||||||
local skipped = false
|
if line.push_event and state.interpreter.event_buffer and state.interpreter.event_type ~= line.push_event then
|
||||||
if line.condition then
|
local v, e = run_line(state, { source = line.source, type = "flush_events" })
|
||||||
local v, e = eval(state, line.condition)
|
if e then return v, e end
|
||||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
if v then return v end
|
||||||
skipped = not truthy(v)
|
|
||||||
end
|
end
|
||||||
if not skipped then
|
-- line types
|
||||||
-- tag decorator
|
if line.type == "condition" then
|
||||||
if line.tag then
|
line.parent_block.last_condition_success = nil
|
||||||
local v, e = eval(state, line.tag)
|
local v, e = eval(state, line.expression)
|
||||||
if not v then return v, ("%s; in tag decorator at %s"):format(e, line.source) end
|
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
||||||
tags:push(state, v)
|
if truthy(v) then
|
||||||
end
|
line.parent_block.last_condition_success = true
|
||||||
-- if line intend to push an event, flush buffer it it's a different event
|
v, e = run_block(state, line.child)
|
||||||
if line.push_event and state.interpreter.event_buffer and state.interpreter.event_type ~= line.push_event then
|
|
||||||
local v, e = run_line(state, { source = line.source, type = "flush_events" })
|
|
||||||
if e then return v, e end
|
if e then return v, e end
|
||||||
if v then return v end
|
if v then return v end
|
||||||
end
|
end
|
||||||
-- line types
|
elseif line.type == "else-condition" then
|
||||||
if line.type == "condition" then
|
if not line.parent_block.last_condition_success then
|
||||||
line.parent_block.last_condition_success = nil
|
|
||||||
local v, e = eval(state, line.expression)
|
local v, e = eval(state, line.expression)
|
||||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
||||||
if truthy(v) then
|
if truthy(v) then
|
||||||
|
|
@ -88,85 +84,68 @@ local function run_line(state, line)
|
||||||
if e then return v, e end
|
if e then return v, e end
|
||||||
if v then return v end
|
if v then return v end
|
||||||
end
|
end
|
||||||
elseif line.type == "else-condition" then
|
end
|
||||||
if not line.parent_block.last_condition_success then
|
elseif line.type == "choice" then
|
||||||
local v, e = eval(state, line.expression)
|
local t, er = eval_text(state, line.text)
|
||||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
if not t then return t, er end
|
||||||
if truthy(v) then
|
table.insert(state.interpreter.choice_available, {
|
||||||
line.parent_block.last_condition_success = true
|
tags = tags:current(state),
|
||||||
v, e = run_block(state, line.child)
|
block = line.child
|
||||||
|
})
|
||||||
|
write_event(state, "choice", t)
|
||||||
|
elseif line.type == "tag" then
|
||||||
|
local v, e = eval(state, line.expression)
|
||||||
|
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
||||||
|
tags:push(state, v)
|
||||||
|
v, e = run_block(state, line.child)
|
||||||
|
tags:pop(state)
|
||||||
|
if e then return v, e end
|
||||||
|
if v then return v end
|
||||||
|
elseif line.type == "return" then
|
||||||
|
local v, e = eval(state, line.expression)
|
||||||
|
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
||||||
|
return v
|
||||||
|
elseif line.type == "text" then
|
||||||
|
local t, er = eval_text(state, line.text)
|
||||||
|
if not t then return t, ("%s; at %s"):format(er, line.source) end
|
||||||
|
write_event(state, "text", t)
|
||||||
|
elseif line.type == "flush_events" then
|
||||||
|
while state.interpreter.event_buffer do
|
||||||
|
local type, buffer = state.interpreter.event_type, state.interpreter.event_buffer
|
||||||
|
state.interpreter.event_type = nil
|
||||||
|
state.interpreter.event_buffer = nil
|
||||||
|
-- yield
|
||||||
|
coroutine.yield(type, buffer)
|
||||||
|
-- run choice
|
||||||
|
if type == "choice" then
|
||||||
|
local sel = state.interpreter.choice_selected
|
||||||
|
state.interpreter.choice_selected = nil
|
||||||
|
if not sel or sel < 1 or sel > #state.interpreter.choice_available then
|
||||||
|
return nil, "invalid choice"
|
||||||
|
else
|
||||||
|
local choice = state.interpreter.choice_available[sel]
|
||||||
|
state.interpreter.choice_available = {}
|
||||||
|
tags:push_lua_no_merge(state, choice.tags)
|
||||||
|
local v, e = run_block(state, choice.block)
|
||||||
|
tags:pop(state)
|
||||||
if e then return v, e end
|
if e then return v, e end
|
||||||
if v then return v end
|
-- discard return value from choice block as the execution is delayed until an event flush
|
||||||
|
-- and we don't want to stop the execution of another function unexpectedly
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif line.type == "choice" then
|
|
||||||
local t, er = eval_text(state, line.text)
|
|
||||||
if not t then return t, er end
|
|
||||||
table.insert(state.interpreter.choice_available, {
|
|
||||||
tags = tags:current(state),
|
|
||||||
block = line.child
|
|
||||||
})
|
|
||||||
write_event(state, "choice", t)
|
|
||||||
elseif line.type == "tag" then
|
|
||||||
local v, e = eval(state, line.expression)
|
|
||||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
|
||||||
tags:push(state, v)
|
|
||||||
v, e = run_block(state, line.child)
|
|
||||||
tags:pop(state)
|
|
||||||
if e then return v, e end
|
|
||||||
if v then return v end
|
|
||||||
elseif line.type == "return" then
|
|
||||||
local v, e = eval(state, line.expression)
|
|
||||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
|
||||||
return v
|
|
||||||
elseif line.type == "text" then
|
|
||||||
local t, er = eval_text(state, line.text)
|
|
||||||
if not t then return t, ("%s; at %s"):format(er, line.source) end
|
|
||||||
write_event(state, "text", t)
|
|
||||||
elseif line.type == "flush_events" then
|
|
||||||
while state.interpreter.event_buffer do
|
|
||||||
local type, buffer = state.interpreter.event_type, state.interpreter.event_buffer
|
|
||||||
state.interpreter.event_type = nil
|
|
||||||
state.interpreter.event_buffer = nil
|
|
||||||
-- yield
|
|
||||||
coroutine.yield(type, buffer)
|
|
||||||
-- run choice
|
|
||||||
if type == "choice" then
|
|
||||||
local sel = state.interpreter.choice_selected
|
|
||||||
state.interpreter.choice_selected = nil
|
|
||||||
if not sel or sel < 1 or sel > #state.interpreter.choice_available then
|
|
||||||
return nil, "invalid choice"
|
|
||||||
else
|
|
||||||
local choice = state.interpreter.choice_available[sel]
|
|
||||||
state.interpreter.choice_available = {}
|
|
||||||
tags:push_lua_no_merge(state, choice.tags)
|
|
||||||
local v, e = run_block(state, choice.block)
|
|
||||||
tags:pop(state)
|
|
||||||
if e then return v, e end
|
|
||||||
-- discard return value from choice block as the execution is delayed until an event flush
|
|
||||||
-- and we don't want to stop the execution of another function unexpectedly
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif line.type ~= "checkpoint" then
|
|
||||||
return nil, ("unknown line type %q; at %s"):format(line.type, line.source)
|
|
||||||
end
|
|
||||||
-- tag decorator
|
|
||||||
if line.tag then
|
|
||||||
tags:pop(state)
|
|
||||||
end
|
|
||||||
-- checkpoint decorator and line
|
|
||||||
if line.checkpoint then
|
|
||||||
state.variables[line.namespace.."👁️"] = {
|
|
||||||
type = "number",
|
|
||||||
value = state.variables[line.namespace.."👁️"].value + 1
|
|
||||||
}
|
|
||||||
state.variables[line.parent_function.namespace.."🏁"] = {
|
|
||||||
type = "string",
|
|
||||||
value = line.name
|
|
||||||
}
|
|
||||||
merge_state(state)
|
|
||||||
end
|
end
|
||||||
|
elseif line.type == "checkpoint" then
|
||||||
|
state.variables[line.namespace.."👁️"] = {
|
||||||
|
type = "number",
|
||||||
|
value = state.variables[line.namespace.."👁️"].value + 1
|
||||||
|
}
|
||||||
|
state.variables[line.parent_function.namespace.."🏁"] = {
|
||||||
|
type = "string",
|
||||||
|
value = line.name
|
||||||
|
}
|
||||||
|
merge_state(state)
|
||||||
|
else
|
||||||
|
return nil, ("unknown line type %q; at %s"):format(line.type, line.source)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -196,9 +175,9 @@ run_block = function(state, block, resume_from_there, i, j)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
end
|
end
|
||||||
-- if we are exiting a checkpoint block, mark it as ran and update checkpoint
|
-- if we are exiting a checkpoint block, mark it as ran and update checkpoint
|
||||||
-- (when resuming from a checkpoint, execution is resumed from inside the checkpoint, the line.checkpoint check in run_line is never called)
|
-- (when resuming from a checkpoint, execution is resumed from inside the checkpoint, the line.type=="checkpoint" check in run_line is never called)
|
||||||
-- (and we want this to be done after executing the checkpoint block anyway)
|
-- (and we want this to be done after executing the checkpoint block anyway)
|
||||||
if block.parent_line and block.parent_line.checkpoint then
|
if block.parent_line and block.parent_line.type == "checkpoint" then
|
||||||
local parent_line = block.parent_line
|
local parent_line = block.parent_line
|
||||||
state.variables[parent_line.namespace.."👁️"] = {
|
state.variables[parent_line.namespace.."👁️"] = {
|
||||||
type = "number",
|
type = "number",
|
||||||
|
|
@ -227,7 +206,7 @@ run_block = function(state, block, resume_from_there, i, j)
|
||||||
elseif parent_line.type == "condition" or parent_line.type == "else-condition" then
|
elseif parent_line.type == "condition" or parent_line.type == "else-condition" then
|
||||||
parent_line.parent_block.last_condition_success = true
|
parent_line.parent_block.last_condition_success = true
|
||||||
end
|
end
|
||||||
if parent_line.type == "tag" or parent_line.tag then
|
if parent_line.type == "tag" then
|
||||||
tags:pop(state)
|
tags:pop(state)
|
||||||
end
|
end
|
||||||
local v, e = run_block(state, parent_line.parent_block, resume_from_there, parent_line.parent_position+1)
|
local v, e = run_block(state, parent_line.parent_block, resume_from_there, parent_line.parent_position+1)
|
||||||
|
|
@ -252,11 +231,6 @@ local function run(state, block, resume_from_there, i, j)
|
||||||
if not v then return v, ("%s; at %s"):format(e, parent_line.source) end
|
if not v then return v, ("%s; at %s"):format(e, parent_line.source) end
|
||||||
table.insert(tags_to_add, v)
|
table.insert(tags_to_add, v)
|
||||||
end
|
end
|
||||||
if parent_line.tag then
|
|
||||||
local v, e = eval(state, parent_line.tag)
|
|
||||||
if not v then return v, ("%s; in tag decorator at %s"):format(e, parent_line.source) end
|
|
||||||
table.insert(tags_to_add, v)
|
|
||||||
end
|
|
||||||
parent_line = parent_line.parent_block.parent_line
|
parent_line = parent_line.parent_block.parent_line
|
||||||
end
|
end
|
||||||
-- re-add tag in desceding order
|
-- re-add tag in desceding order
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,6 @@ local parse_text
|
||||||
local function parse(state)
|
local function parse(state)
|
||||||
for _, l in ipairs(state.queued_lines) do
|
for _, l in ipairs(state.queued_lines) do
|
||||||
local line, namespace = l.line, l.namespace
|
local line, namespace = l.line, l.namespace
|
||||||
-- decorators
|
|
||||||
if line.condition then
|
|
||||||
if line.condition:match("[^%s]") then
|
|
||||||
local exp, rem = expression(line.condition, state, namespace)
|
|
||||||
if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
|
|
||||||
if rem:match("[^%s]") then return nil, ("expected end of expression before %q in condition decorator; at %s"):format(rem, line.source) end
|
|
||||||
line.condition = exp
|
|
||||||
else
|
|
||||||
line.condition = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if line.tag then
|
|
||||||
if line.tag:match("[^%s]") then
|
|
||||||
local exp, rem = expression(line.tag, state, namespace)
|
|
||||||
if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
|
|
||||||
if rem:match("[^%s]") then return nil, ("expected end of expression before %q in condition decorator; at %s"):format(rem, line.source) end
|
|
||||||
line.tag = exp
|
|
||||||
else
|
|
||||||
line.tag = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- expressions
|
-- expressions
|
||||||
if line.expression then
|
if line.expression then
|
||||||
local exp, rem = expression(line.expression, state, namespace)
|
local exp, rem = expression(line.expression, state, namespace)
|
||||||
|
|
|
||||||
|
|
@ -15,79 +15,6 @@ local function parse_line(line, state, namespace)
|
||||||
r.remove_from_block_ast = true
|
r.remove_from_block_ast = true
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
-- decorators
|
|
||||||
while l:match("^..+[~#]") or l:match("^..+§") do
|
|
||||||
-- condition
|
|
||||||
if l:match("^..+%~.-$") then
|
|
||||||
local expr
|
|
||||||
l, expr = l:match("^(.-)%s*%~(.-)$")
|
|
||||||
r.condition = expr
|
|
||||||
-- checkpoint
|
|
||||||
elseif l:match("^..+§.-$") then
|
|
||||||
-- get identifier
|
|
||||||
local name
|
|
||||||
l, name = l:match("^(.-)%s*§(.-)$")
|
|
||||||
local identifier, rem = name:match("^("..identifier_pattern..")(.-)$")
|
|
||||||
if not identifier then return nil, ("no valid identifier in checkpoint decorator %q; at %s"):format(identifier, line.source) end
|
|
||||||
-- format identifier
|
|
||||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
|
|
||||||
-- get alias
|
|
||||||
if rem:match("^%:") then
|
|
||||||
local content = rem:sub(2)
|
|
||||||
local alias, rem2 = content:match("^("..identifier_pattern..")(.-)$")
|
|
||||||
if not alias then return nil, ("expected an identifier in alias in checkpoint decorator, but got %q; at %s"):format(content, line.source) end
|
|
||||||
if rem2:match("[^%s]") then return nil, ("expected end-of-line after identifier in alias in checkpoint decorator, but got %q; at %s"):format(rem2, line.source) end
|
|
||||||
-- format alias
|
|
||||||
local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias))
|
|
||||||
-- define alias
|
|
||||||
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
|
|
||||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
|
|
||||||
end
|
|
||||||
state.aliases[aliasfqm] = fqm
|
|
||||||
elseif rem:match("[^%s]") then
|
|
||||||
return nil, ("expected end-of-line after identifier in checkpoint decorator, but got %q; at %s"):format(rem, line.source)
|
|
||||||
end
|
|
||||||
-- define checkpoint
|
|
||||||
namespace = fqm.."."
|
|
||||||
r.checkpoint = true
|
|
||||||
r.parent_function = true
|
|
||||||
r.namespace = fqm.."."
|
|
||||||
r.name = fqm
|
|
||||||
if not state.functions[fqm] then
|
|
||||||
state.functions[fqm] = {
|
|
||||||
{
|
|
||||||
arity = 0,
|
|
||||||
value = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if not state.variables[fqm..".👁️"] then
|
|
||||||
state.variables[fqm..".👁️"] = {
|
|
||||||
type = "number",
|
|
||||||
value = 0
|
|
||||||
}
|
|
||||||
end
|
|
||||||
-- define alias for 👁️
|
|
||||||
local seen_alias = state.builtin_aliases["👁️"]
|
|
||||||
if seen_alias then
|
|
||||||
local alias = ("%s.%s"):format(fqm, seen_alias)
|
|
||||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
|
||||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".👁️", state.aliases[alias], line.source)
|
|
||||||
end
|
|
||||||
state.aliases[alias] = fqm..".👁️"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
table.insert(state.functions[fqm], {
|
|
||||||
arity = 0,
|
|
||||||
value = r
|
|
||||||
})
|
|
||||||
end
|
|
||||||
-- tag
|
|
||||||
elseif l:match("^..+%#.-$") then
|
|
||||||
local expr
|
|
||||||
l, expr = l:match("^(.-)%s*%#(.-)$")
|
|
||||||
r.tag = expr
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- else-condition & condition
|
-- else-condition & condition
|
||||||
if l:match("^~~?") then
|
if l:match("^~~?") then
|
||||||
r.type = l:match("^~~") and "else-condition" or "condition"
|
r.type = l:match("^~~") and "else-condition" or "condition"
|
||||||
|
|
@ -169,7 +96,6 @@ local function parse_line(line, state, namespace)
|
||||||
end
|
end
|
||||||
-- store parent function and run checkpoint when line is read
|
-- store parent function and run checkpoint when line is read
|
||||||
if r.type == "checkpoint" then
|
if r.type == "checkpoint" then
|
||||||
r.checkpoint = true
|
|
||||||
r.parent_function = true
|
r.parent_function = true
|
||||||
end
|
end
|
||||||
-- don't keep function node in block AST
|
-- don't keep function node in block AST
|
||||||
|
|
@ -336,52 +262,78 @@ end
|
||||||
-- * nil, err: in case of error
|
-- * nil, err: in case of error
|
||||||
local function parse_block(indented, state, namespace, parent_function)
|
local function parse_block(indented, state, namespace, parent_function)
|
||||||
local block = { type = "block" }
|
local block = { type = "block" }
|
||||||
local lastLine -- last line AST
|
for _, l in ipairs(indented) do
|
||||||
for i, l in ipairs(indented) do
|
|
||||||
-- parsable line
|
-- parsable line
|
||||||
if l.content then
|
local ast, err = parse_line(l, state, namespace)
|
||||||
local ast, err = parse_line(l, state, namespace)
|
if err then return nil, err end
|
||||||
if err then return nil, err end
|
-- store parent function
|
||||||
lastLine = ast
|
if ast.parent_function then ast.parent_function = parent_function end
|
||||||
-- store parent function
|
-- add to block AST
|
||||||
if ast.parent_function then ast.parent_function = parent_function end
|
if not ast.remove_from_block_ast then
|
||||||
-- add to block AST
|
ast.parent_block = block
|
||||||
if not ast.remove_from_block_ast then
|
-- add ast node
|
||||||
ast.parent_block = block
|
ast.parent_position = #block+1
|
||||||
-- add ast node
|
table.insert(block, ast)
|
||||||
ast.parent_position = #block+1
|
end
|
||||||
if ast.replace_with then
|
-- add child
|
||||||
if indented[i+1].content then
|
if ast.child then ast.child = { type = "block", parent_line = ast } end
|
||||||
table.insert(indented, i+1, { content = ast.replace_with, source = l.source })
|
-- queue in expression evalution
|
||||||
else
|
table.insert(state.queued_lines, { namespace = ast.namespace or namespace, line = ast })
|
||||||
table.insert(indented, i+2, { content = ast.replace_with, source = l.source }) -- if line has children
|
|
||||||
end
|
-- indented block (ignore block comments)
|
||||||
else
|
if l.children and ast.type ~= "comment" then
|
||||||
table.insert(block, ast)
|
if not ast.child then
|
||||||
end
|
return nil, ("line %s (%s) can't have children"):format(ast.source, ast.type)
|
||||||
end
|
|
||||||
-- add child
|
|
||||||
if ast.child then ast.child = { type = "block", parent_line = ast } end
|
|
||||||
-- queue in expression evalution
|
|
||||||
table.insert(state.queued_lines, { namespace = ast.namespace or namespace, line = ast })
|
|
||||||
-- indented (ignore block comments)
|
|
||||||
elseif lastLine.type ~= "comment" then
|
|
||||||
if not lastLine.child then
|
|
||||||
return nil, ("line %s (%s) can't have children"):format(lastLine.source, lastLine.type)
|
|
||||||
else
|
else
|
||||||
local r, e = parse_block(l, state, lastLine.namespace or namespace, lastLine.type == "function" and lastLine or parent_function)
|
local r, e = parse_block(l.children, state, ast.namespace or namespace, ast.type == "function" and ast or parent_function)
|
||||||
if not r then return r, e end
|
if not r then return r, e end
|
||||||
r.parent_line = lastLine
|
r.parent_line = ast
|
||||||
lastLine.child = r
|
ast.child = r
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return block
|
return block
|
||||||
end
|
end
|
||||||
|
|
||||||
--- returns the nested list of lines {content="", line=1}, grouped by indentation
|
-- returns new_indented
|
||||||
|
local function transform_indented(indented)
|
||||||
|
local i = 1
|
||||||
|
while i <= #indented do
|
||||||
|
local l = indented[i]
|
||||||
|
|
||||||
|
-- condition decorator
|
||||||
|
if l.content:match("^.-%s*[^~]%~[^#~$]-$") then
|
||||||
|
local decorator
|
||||||
|
l.content, decorator = l.content:match("^(..-)%s*(%~[^#~$]-)$")
|
||||||
|
indented[i] = { content = decorator, source = l.source, children = { l } }
|
||||||
|
-- tag decorator
|
||||||
|
elseif l.content:match("^..-%s*%#[^#~$]-$") then
|
||||||
|
local decorator
|
||||||
|
l.content, decorator = l.content:match("^(..-)%s*(%#[^#~$]-)$")
|
||||||
|
indented[i] = { content = decorator, source = l.source, children = { l } }
|
||||||
|
-- function decorator
|
||||||
|
elseif l.content:match("^..-%s*%$[^#~$]-$") then
|
||||||
|
local name
|
||||||
|
l.content, name = l.content:match("^(..-)%s*%$([^#~$]-)$")
|
||||||
|
indented[i] = { content = "~"..name, source = l.source }
|
||||||
|
table.insert(indented, i+1, { content = "$"..name, source = l.source, children = { l } })
|
||||||
|
i = i + 1 -- $ line should not contain any decorator anymore
|
||||||
|
else
|
||||||
|
i = i + 1 -- only increment when no decorator, as there may be several decorators per line
|
||||||
|
end
|
||||||
|
|
||||||
|
-- indented block
|
||||||
|
if l.children then
|
||||||
|
transform_indented(l.children)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return indented
|
||||||
|
end
|
||||||
|
|
||||||
|
--- returns the nested list of lines {content="", line=1, children={lines...} or nil}, parsing indentation
|
||||||
-- multiple empty lines are merged
|
-- multiple empty lines are merged
|
||||||
-- * list, last line
|
-- * list, last line, insert_empty_line: in case of success
|
||||||
|
-- * nil, err: in case of error
|
||||||
local function parse_indent(lines, source, i, indentLevel, insert_empty_line)
|
local function parse_indent(lines, source, i, indentLevel, insert_empty_line)
|
||||||
i = i or 1
|
i = i or 1
|
||||||
indentLevel = indentLevel or 0
|
indentLevel = indentLevel or 0
|
||||||
|
|
@ -396,9 +348,14 @@ local function parse_indent(lines, source, i, indentLevel, insert_empty_line)
|
||||||
end
|
end
|
||||||
table.insert(indented, { content = line, source = ("%s:%s"):format(source, i) })
|
table.insert(indented, { content = line, source = ("%s:%s"):format(source, i) })
|
||||||
elseif #indent > indentLevel then
|
elseif #indent > indentLevel then
|
||||||
local t
|
if #indented == 0 then
|
||||||
t, i, insert_empty_line = parse_indent(lines, source, i, #indent, insert_empty_line)
|
return nil, ("unexpected indentation; at %s:%s"):format(source, i)
|
||||||
table.insert(indented, t)
|
else
|
||||||
|
local t
|
||||||
|
t, i, insert_empty_line = parse_indent(lines, source, i, #indent, insert_empty_line)
|
||||||
|
if not t then return nil, i end
|
||||||
|
indented[#indented].children = t
|
||||||
|
end
|
||||||
else
|
else
|
||||||
return indented, i-1, insert_empty_line
|
return indented, i-1, insert_empty_line
|
||||||
end
|
end
|
||||||
|
|
@ -426,17 +383,19 @@ end
|
||||||
local function parse(state, s, name, source)
|
local function parse(state, s, name, source)
|
||||||
-- parse lines
|
-- parse lines
|
||||||
local lines = parse_lines(s)
|
local lines = parse_lines(s)
|
||||||
local indented = parse_indent(lines, source or name)
|
local indented, e = parse_indent(lines, source or name)
|
||||||
|
if not indented then return nil, e end
|
||||||
-- wrap in named function if neccessary
|
-- wrap in named function if neccessary
|
||||||
if name ~= "" then
|
if name ~= "" then
|
||||||
if not name:match("^"..identifier_pattern.."$") then
|
if not name:match("^"..identifier_pattern.."$") then
|
||||||
return nil, ("invalid function name %q"):format(name)
|
return nil, ("invalid function name %q"):format(name)
|
||||||
end
|
end
|
||||||
indented = {
|
indented = {
|
||||||
{ content = "$ "..name, source = ("%s:%s"):format(source or name, 0) },
|
{ content = "$ "..name, source = ("%s:%s"):format(source or name, 0), children = indented },
|
||||||
indented
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
-- transform ast
|
||||||
|
indented = transform_indented(indented)
|
||||||
-- parse
|
-- parse
|
||||||
local root, err = parse_block(indented, state, "")
|
local root, err = parse_block(indented, state, "")
|
||||||
if not root then return nil, err end
|
if not root then return nil, err end
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,7 @@ else
|
||||||
if args["write-new"] and e:match("No such file") then
|
if args["write-new"] and e:match("No such file") then
|
||||||
write_result(filebase, result)
|
write_result(filebase, result)
|
||||||
print("Written result file for "..filebase)
|
print("Written result file for "..filebase)
|
||||||
|
success = success + 1
|
||||||
elseif not args.silent then
|
elseif not args.silent then
|
||||||
print("> "..namespace)
|
print("> "..namespace)
|
||||||
print(e)
|
print(e)
|
||||||
|
|
|
||||||
3
test/tests/function decorator scope explicit call.ans
Normal file
3
test/tests/function decorator scope explicit call.ans
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
a.👁️: {a.👁️} $ a
|
||||||
|
|
||||||
|
~ a()
|
||||||
9
test/tests/function decorator scope implicit call.ans
Normal file
9
test/tests/function decorator scope implicit call.ans
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
$ f
|
||||||
|
ko
|
||||||
|
a.👁️: {a.👁️} $ a
|
||||||
|
ok
|
||||||
|
|
||||||
|
~ f.a
|
||||||
|
|
||||||
|
In function:
|
||||||
|
~ f
|
||||||
37
test/tests/function decorator scope implicit call.lua
Normal file
37
test/tests/function decorator scope implicit call.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
local _={}
|
||||||
|
_[15]={}
|
||||||
|
_[14]={}
|
||||||
|
_[13]={}
|
||||||
|
_[12]={}
|
||||||
|
_[11]={}
|
||||||
|
_[10]={data="ok",tags=_[15]}
|
||||||
|
_[9]={data="a.\240\159\145\129\239\184\143: 1",tags=_[14]}
|
||||||
|
_[8]={data="ko",tags=_[13]}
|
||||||
|
_[7]={data="In function:",tags=_[12]}
|
||||||
|
_[6]={data="a.\240\159\145\129\239\184\143: 0",tags=_[11]}
|
||||||
|
_[5]={_[7],_[8],_[9],_[10]}
|
||||||
|
_[4]={_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[5]}
|
||||||
|
_[1]={"text",_[4]}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a.👁️: 0",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "In function:",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "ko",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "a.👁️: 1",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
1
test/tests/function decorator scope.ans
Normal file
1
test/tests/function decorator scope.ans
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
a.👁️: {a.👁️} $ a
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[5]={}
|
_[5]={}
|
||||||
_[4]={data="a.\240\159\145\129\239\184\143: 0",tags=_[5]}
|
_[4]={tags=_[5],data="a.\240\159\145\129\239\184\143: 0"}
|
||||||
_[3]={_[4]}
|
_[3]={_[4]}
|
||||||
_[2]={"return"}
|
_[2]={"return"}
|
||||||
_[1]={"text",_[3]}
|
_[1]={"text",_[3]}
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
a.👁️: {a.👁️} § a
|
|
||||||
|
|
||||||
~ a()
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
$ f
|
|
||||||
ko
|
|
||||||
a.👁️: {a.👁️} § a
|
|
||||||
ok
|
|
||||||
|
|
||||||
~ f.a
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
local _={}
|
|
||||||
_[7]={}
|
|
||||||
_[6]={}
|
|
||||||
_[5]={data="ok",tags=_[7]}
|
|
||||||
_[4]={data="a.\240\159\145\129\239\184\143: 0",tags=_[6]}
|
|
||||||
_[3]={_[4],_[5]}
|
|
||||||
_[2]={"return"}
|
|
||||||
_[1]={"text",_[3]}
|
|
||||||
return {_[1],_[2]}
|
|
||||||
--[[
|
|
||||||
{ "text", { {
|
|
||||||
data = "a.👁️: 0",
|
|
||||||
tags = {}
|
|
||||||
}, {
|
|
||||||
data = "ok",
|
|
||||||
tags = {}
|
|
||||||
} } }
|
|
||||||
{ "return" }
|
|
||||||
]]--
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
a.👁️: {a.👁️} § a
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue