mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Add "function return" and "scoped function return" injections, allow children for return lines
This commit is contained in:
parent
b60f53df01
commit
933e8fb0ee
7 changed files with 131 additions and 72 deletions
34
LANGUAGE.md
34
LANGUAGE.md
|
|
@ -281,15 +281,6 @@ Checkpoints always have the following variable defined in its namespace by defau
|
|||
Tagged with a red color and blink.
|
||||
```
|
||||
|
||||
#### Lines that can't have children:
|
||||
|
||||
* `:`: variable declaration. Followed by an [identifier](#identifiers) (with eventually an [alias](#aliases)), a `=` and an [expression](#expressions). Defines a variable with a default value and this identifier in the current [namespace]("identifiers"). The expression is not evaluated instantly, but the first time the variable is used.
|
||||
|
||||
```
|
||||
:foo = 42
|
||||
:bar : alias = 12
|
||||
```
|
||||
|
||||
* `@`: return line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. Exit the current function and returns the expression's value.
|
||||
|
||||
```
|
||||
|
|
@ -299,6 +290,23 @@ $ hey
|
|||
{hey} = 5
|
||||
```
|
||||
|
||||
If this line has children, they will be ran _after_ evaluating the returned expression but _before_ exiting the current function. If the children return a value, it is used instead.
|
||||
|
||||
```
|
||||
(Returns 0 and print 5)
|
||||
$ fn
|
||||
:i=0
|
||||
|
||||
@i
|
||||
~ i:=5
|
||||
{i}
|
||||
|
||||
(Returns 3)
|
||||
$ g
|
||||
@0
|
||||
@3
|
||||
```
|
||||
|
||||
Please note that Anselme will discard returns values sent from within a choice block. Returns inside choice block still have the expected behaviour of stopping the execution of the choice block.
|
||||
|
||||
This is the case because choice blocks are not ran right as they are read, but only at the next event flush (i.e. empty line). This means that if there is no flush in the function itself, the choice will be ran *after* the function has already been executed and returning a value at this point makes no sense:
|
||||
|
|
@ -315,7 +323,15 @@ $ f
|
|||
Yes.
|
||||
|
||||
(Choice block is actually ran right before the "Yes" line, when the choice event is flushed.)
|
||||
```
|
||||
|
||||
#### Lines that can't have children:
|
||||
|
||||
* `:`: variable declaration. Followed by an [identifier](#identifiers) (with eventually an [alias](#aliases)), a `=` and an [expression](#expressions). Defines a variable with a default value and this identifier in the current [namespace]("identifiers"). The expression is not evaluated instantly, but the first time the variable is used.
|
||||
|
||||
```
|
||||
:foo = 42
|
||||
:bar : alias = 12
|
||||
```
|
||||
|
||||
* empty line: flush the event buffer, i.e., if there are any pending lines of text or choices, send them to your game. See [Event buffer](#event-buffer). This line always keep the same identation as the last non-empty line, so you don't need to put invisible whitespace on an empty-looking line. Is also automatically added at the end of a file.
|
||||
|
|
|
|||
66
anselme.lua
66
anselme.lua
|
|
@ -22,6 +22,7 @@ local preparse = require(anselme_root.."parser.preparser")
|
|||
local postparse = require(anselme_root.."parser.postparser")
|
||||
local expression = require(anselme_root.."parser.expression")
|
||||
local eval = require(anselme_root.."interpreter.expression")
|
||||
local injections = require(anselme_root.."parser.common").injections
|
||||
local run_line = require(anselme_root.."interpreter.interpreter").run_line
|
||||
local run = require(anselme_root.."interpreter.interpreter").run
|
||||
local to_lua = require(anselme_root.."interpreter.common").to_lua
|
||||
|
|
@ -230,7 +231,7 @@ local vm_mt = {
|
|||
-- Always included in saved variables.
|
||||
-- * language: string, built-in language file to load
|
||||
-- * inject directory: string, directory that may contain "function start.ans", "checkpoint end.ans", etc. which content will be used to setup
|
||||
-- the custom code injection methods like vm:injectfunctionstart
|
||||
-- the custom code injection methods (see vm:setinjection)
|
||||
-- * global directory: string, path of global script directory. Every script file and subdirectory in the path will be loaded in the global namespace.
|
||||
-- * start expression: string, expression that will be ran when starting the game
|
||||
-- * main file, if defined in config.ans
|
||||
|
|
@ -266,10 +267,10 @@ local vm_mt = {
|
|||
end
|
||||
-- load injections
|
||||
if self.game.inject_directory then
|
||||
for _, inject in ipairs{"function start", "function end", "scoped function start", "scoped function end", "checkpoint start", "checkpoint end"} do
|
||||
for inject, ninject in pairs(injections) do
|
||||
local f = io.open(path.."/"..self.game.inject_directory.."/"..inject..".ans", "r")
|
||||
if f then
|
||||
self.state.inject[inject:gsub(" ", "_")] = f:read("*a")
|
||||
self.state.inject[ninject] = f:read("*a")
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
|
@ -376,47 +377,22 @@ local vm_mt = {
|
|||
self.state.builtin_aliases["🏁"] = reached
|
||||
return self
|
||||
end,
|
||||
--- set some code that will be added at the start of every non-scoped function defined after this is called
|
||||
-- nil to disable
|
||||
-- can typically be used to define variables for every function like 👁️
|
||||
--- set some code that will be injected at specific places in all code loaded after this is called
|
||||
-- possible inject types:
|
||||
-- * "function start": injected at the start of every non-scoped function
|
||||
-- * "function end": injected at the end of every non-scoped function
|
||||
-- * "function return": injected at the end of each return's children that is contained in a non-scoped function
|
||||
-- * "checkpoint start": injected at the start of every checkpoint
|
||||
-- * "checkpoint end": injected at the end of every checkpoint
|
||||
-- * "scoped function start": injected at the start of every scoped function
|
||||
-- * "scoped function end": injected at the end of every scoped function
|
||||
-- * "scoped function return": injected at the end of each return's children that is contained in a scoped function
|
||||
-- set to nil to disable
|
||||
-- can typically be used to define variables for every function like 👁️, setting some value on every function resume, etc.
|
||||
-- return self
|
||||
injectfunctionstart = function(self, code)
|
||||
self.state.inject.function_start = code
|
||||
return self
|
||||
end,
|
||||
--- same as injectfunctionstart, but inject code at the start of every scoped function
|
||||
-- nil to disable
|
||||
-- return self
|
||||
injectscopedfunctionstart = function(self, code)
|
||||
self.state.inject.scoped_function_start = code
|
||||
return self
|
||||
end,
|
||||
--- same as injectfunctionstart, but inject code at the start of every checkpoint
|
||||
-- nil to disable
|
||||
-- return self
|
||||
injectcheckpointstart = function(self, code)
|
||||
self.state.inject.checkpoint_start = code
|
||||
return self
|
||||
end,
|
||||
--- same as injectfunctionstart, but inject code at the end of every non-scoped function
|
||||
-- nil to disable
|
||||
-- return self
|
||||
injectfunctionend = function(self, code)
|
||||
self.state.inject.function_end = code
|
||||
return self
|
||||
end,
|
||||
--- same as injectfunctionstart, but inject code at the end of every scoped function
|
||||
-- nil to disable
|
||||
-- return self
|
||||
injectscopedfunctionend = function(self, code)
|
||||
self.state.inject.scoped_function_end = code
|
||||
return self
|
||||
end,
|
||||
--- same as injectfunctionend, but inject code at the end of every checkpoint
|
||||
-- nil to disable
|
||||
-- return self
|
||||
injectcheckpointend = function(self, code)
|
||||
self.state.inject.checkpoint_end = code
|
||||
setinjection = function(self, inject, code)
|
||||
assert(injections[inject], ("unknown injection type %q"):format(inject))
|
||||
self.state.inject[injections[inject]] = code
|
||||
return self
|
||||
end,
|
||||
|
||||
|
|
@ -614,9 +590,7 @@ return setmetatable(anselme, {
|
|||
-- global state
|
||||
local state = {
|
||||
inject = {
|
||||
function_start = nil, function_end = nil,
|
||||
scoped_function_start = nil, scoped_function_end = nil,
|
||||
checkpoint_start = nil, checkpoint_end = nil
|
||||
-- function_start = "code block...", ...
|
||||
},
|
||||
feature_flags = {
|
||||
["strip trailing spaces"] = true,
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ run_line = function(state, line)
|
|||
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
|
||||
local cv, ce = run_block(state, line.child)
|
||||
if ce then return cv, ce end
|
||||
if cv then return cv end
|
||||
return v
|
||||
elseif line.type == "text" then
|
||||
local v, e = events:make_space_for(state, "text") -- do this before any evaluation start
|
||||
|
|
|
|||
|
|
@ -70,6 +70,12 @@ common = {
|
|||
-- decorators
|
||||
["\\$"] = "$"
|
||||
},
|
||||
-- list of possible injections and their associated name in vm.state.inject
|
||||
injections = {
|
||||
["function start"] = "function_start", ["function end"] = "function_end", ["function return"] = "function_return",
|
||||
["scoped function start"] = "scoped_function_start", ["scoped function end"] = "scoped_function_end", ["scoped function return"] = "scoped_function_return",
|
||||
["checkpoint start"] = "checkpoint_start", ["checkpoint end"] = "checkpoint_end"
|
||||
},
|
||||
--- escape a string to be used as an exact match pattern
|
||||
escape = function(str)
|
||||
if not escapeCache[str] then
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
local format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature, copy
|
||||
local format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature, copy, injections
|
||||
|
||||
local parse_indented
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ end
|
|||
--- parse a single line into AST
|
||||
-- * ast: if success
|
||||
-- * nil, error: in case of error
|
||||
local function parse_line(line, state, namespace)
|
||||
local function parse_line(line, state, namespace, parent_function)
|
||||
local l = line.content
|
||||
local r = {
|
||||
source = line.source
|
||||
|
|
@ -59,7 +59,7 @@ local function parse_line(line, state, namespace)
|
|||
r.child = true
|
||||
-- store parent function and run checkpoint when line is read
|
||||
if r.type == "checkpoint" then
|
||||
r.parent_function = true
|
||||
r.parent_function = parent_function
|
||||
end
|
||||
-- don't keep function node in block AST
|
||||
if r.type == "function" then
|
||||
|
|
@ -311,13 +311,29 @@ local function parse_line(line, state, namespace)
|
|||
-- return
|
||||
elseif l:match("^%@") then
|
||||
r.type = "return"
|
||||
r.parent_function = true
|
||||
r.child = true
|
||||
r.parent_function = parent_function
|
||||
local expr = l:match("^%@(.*)$")
|
||||
if expr:match("[^%s]") then
|
||||
r.expression = expr
|
||||
else
|
||||
r.expression = "()"
|
||||
end
|
||||
-- custom code injection
|
||||
if not line.children then line.children = {} end
|
||||
if parent_function.scoped then
|
||||
if state.inject.scoped_function_return then
|
||||
for _, ll in ipairs(state.inject.scoped_function_return) do
|
||||
table.insert(line.children, copy(ll))
|
||||
end
|
||||
end
|
||||
else
|
||||
if state.inject.function_return then
|
||||
for _, ll in ipairs(state.inject.function_return) do
|
||||
table.insert(line.children, copy(ll))
|
||||
end
|
||||
end
|
||||
end
|
||||
-- text
|
||||
elseif l:match("[^%s]") then
|
||||
r.type = "text"
|
||||
|
|
@ -337,10 +353,8 @@ local function parse_block(indented, state, namespace, parent_function)
|
|||
local block = { type = "block" }
|
||||
for _, l in ipairs(indented) do
|
||||
-- parsable line
|
||||
local ast, err = parse_line(l, state, namespace)
|
||||
local ast, err = parse_line(l, state, namespace, parent_function)
|
||||
if err then return nil, err end
|
||||
-- store parent function
|
||||
if ast.parent_function then ast.parent_function = parent_function end
|
||||
-- add to block AST
|
||||
if not ast.remove_from_block_ast then
|
||||
ast.parent_block = block
|
||||
|
|
@ -476,11 +490,7 @@ local function parse(state, s, name, source)
|
|||
if not indented then return nil, e end
|
||||
-- build state proxy
|
||||
local state_proxy = {
|
||||
inject = {
|
||||
function_start = nil, function_end = nil,
|
||||
scoped_function_start = nil, scoped_function_end = nil,
|
||||
checkpoint_start = nil, checkpoint_end = nil
|
||||
},
|
||||
inject = {},
|
||||
aliases = setmetatable({}, { __index = state.aliases }),
|
||||
variables = setmetatable({}, { __index = state.aliases }),
|
||||
functions = setmetatable({}, {
|
||||
|
|
@ -500,11 +510,11 @@ local function parse(state, s, name, source)
|
|||
global_state = state
|
||||
}
|
||||
-- parse injects
|
||||
for _, inject in ipairs{"function_start", "function_end", "scoped_function_start", "scoped_function_end", "checkpoint_start", "checkpoint_end"} do
|
||||
if state.inject[inject] then
|
||||
local inject_indented, err = parse_indented(state.inject[inject], nil, "injected "..inject:gsub("_", " "))
|
||||
for inject, ninject in pairs(injections) do
|
||||
if state.inject[ninject] then
|
||||
local inject_indented, err = parse_indented(state.inject[ninject], nil, "injected "..inject)
|
||||
if not inject_indented then return nil, err end
|
||||
state_proxy.inject[inject] = inject_indented
|
||||
state_proxy.inject[ninject] = inject_indented
|
||||
end
|
||||
end
|
||||
-- parse
|
||||
|
|
@ -535,7 +545,7 @@ end
|
|||
|
||||
package.loaded[...] = parse
|
||||
local common = require((...):gsub("preparser$", "common"))
|
||||
format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature = common.format_identifier, common.identifier_pattern, common.escape, common.special_functions_names, common.pretty_signature, common.signature
|
||||
format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature, injections = common.format_identifier, common.identifier_pattern, common.escape, common.special_functions_names, common.pretty_signature, common.signature, common.injections
|
||||
copy = require((...):gsub("parser%.preparser$", "common")).copy
|
||||
|
||||
return parse
|
||||
|
|
|
|||
13
test/tests/return children.ans
Normal file
13
test/tests/return children.ans
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
$ fn
|
||||
:i=0
|
||||
@i
|
||||
~ i:=5
|
||||
{i}
|
||||
|
||||
{fn} = 50
|
||||
|
||||
$ g
|
||||
@0
|
||||
@3
|
||||
|
||||
{g} = 3
|
||||
37
test/tests/return children.lua
Normal file
37
test/tests/return children.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
local _={}
|
||||
_[15]={}
|
||||
_[14]={}
|
||||
_[13]={}
|
||||
_[12]={}
|
||||
_[11]={}
|
||||
_[10]={text=" = 3",tags=_[15]}
|
||||
_[9]={text="3",tags=_[14]}
|
||||
_[8]={text=" = 50",tags=_[13]}
|
||||
_[7]={text="0",tags=_[12]}
|
||||
_[6]={text="5",tags=_[11]}
|
||||
_[5]={_[9],_[10]}
|
||||
_[4]={_[6],_[7],_[8]}
|
||||
_[3]={"return"}
|
||||
_[2]={"text",_[5]}
|
||||
_[1]={"text",_[4]}
|
||||
return {_[1],_[2],_[3]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "5"
|
||||
}, {
|
||||
tags = {},
|
||||
text = "0"
|
||||
}, {
|
||||
tags = {},
|
||||
text = " = 50"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "3"
|
||||
}, {
|
||||
tags = {},
|
||||
text = " = 3"
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
Loading…
Add table
Add a link
Reference in a new issue