1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00

Compare commits

..

2 commits

11 changed files with 425 additions and 70 deletions

View file

@ -5,8 +5,56 @@ local Event = ast.abstract.Event
local operator_priority = require("anselme.common").operator_priority local operator_priority = require("anselme.common").operator_priority
--- ChoiceEventData represent the data returned by an event with the type `"choice"`.
-- See the [language documentation](language.md#choices) for more details on how to create a choice event.
--
-- A ChoiceEventData contains a list of [LuaText](#luatext), each LuaText representing a separate choice of the choice event.
--
-- For example, the following Anselme script:
--
-- ```
-- *| Yes!
-- *| No.
-- ```
-- will return a choice event containing two LuaTexts, the first containing the text "Yes!" and the second "No.".
--
-- Usage:
-- ```lua
-- current_choice = nil
-- waiting_for_choice = false
--
-- -- in your anselem event handling loop:
-- if not waiting_for_choice then
-- local event_type, event_data = run_state:step()
-- if event_type == "choice" then
-- -- event_data is a ChoiceEventData, i.e. a list of LuaText
-- for i, luatext in ipairs(event_data) do
-- write(("Choice number %s:"):format(i))
-- -- luatext is a list of text parts { text = "text string", tags = { ... } }
-- for _, textpart in ipairs(luatext) do
-- write_choice_part_with_color(textpart.text, textpart.tags.color)
-- end
-- else
-- -- handle other event types...
-- end
-- current_choice = event_data
-- waiting_for_choice = true
-- end
-- end
--
-- -- somewhere in your code where choices are selected
-- current_choice:select(choice_number)
-- waiting_for_choice = false
-- ```
-- @title ChoiceEventData
local ChoiceEventData = class { local ChoiceEventData = class {
-- [1] = LuaText, ...
_selected = nil, _selected = nil,
--- Choose the choice at position `choice` (number).
--
-- A choice must be selected after receiving a choice event and before calling `:step` again.
choose = function(self, choice) choose = function(self, choice)
self._selected = choice self._selected = choice
end end
@ -36,7 +84,7 @@ Choice = ast.abstract.Runtime(Event) {
build_event_data = function(self, state, event_buffer) build_event_data = function(self, state, event_buffer)
local l = ChoiceEventData:new() local l = ChoiceEventData:new()
for _, c in event_buffer:iter(state) do for _, c in event_buffer:iter(state) do
table.insert(l, c.text) table.insert(l, c.text:to_lua(state))
end end
return l return l
end, end,

View file

@ -102,7 +102,7 @@ Struct = ast.abstract.Runtime {
to_lua = function(self, state) to_lua = function(self, state)
local l = {} local l = {}
for _, e in ipairs(self.table) do for _, e in pairs(self.table) do
l[e[1]:to_lua(state)] = e[2]:to_lua(state) l[e[1]:to_lua(state)] = e[2]:to_lua(state)
end end
return l return l

View file

@ -5,16 +5,101 @@ local ArgumentTuple, Struct
local to_anselme = require("anselme.common.to_anselme") local to_anselme = require("anselme.common.to_anselme")
--- A Lua-friendly representation of an Anselme Text value.
-- They appear in both TextEventData and ChoiceEventData to represent the text that has to be shown.
--
-- It contains a list of _text parts_, which are parts of a single text, each part potentially having differrent tags attached.
-- A text will typically only consist of a single part unless it was built using text interpolation.
--
-- Each text part is a table containing `text` (string) and `tags` (table) properties, for example: `{ text = "text part string", tags = { color = "red" } }`.
-- @title LuaText
-- @defer lua text
local LuaText
LuaText = class {
-- [1] = { text = "string", tags = { tag_name = value, ... } }, ...
_state = nil,
--- Anselme Text value this was created from. For advanced usage only. See the source file [Text.lua](anselme/ast/Text.lua) for more information.
-- @defer lua text
raw = nil,
init = function(self, text, state)
self._state = state
self.raw = text
for _, e in ipairs(text.list) do
table.insert(self, { text = e[1]:to_lua(state), tags = e[2]:to_lua(state) })
end
end,
--- Returns a text representation of the LuaText, using Anselme's default formatting. Useful for debugging.
--
-- Usage: `print(luatext)`
-- @defer lua text
__tostring = function(self)
return self.raw:format(self._state)
end
}
--- TextEventData represent the data returned by an event with the type `"text"`.
-- See the [language documentation](language.md#texts) for more details on how to create a text event.
--
-- A TextEventData contains a list of [LuaText](#luatext), each LuaText representing a separate line of the text event.
--
-- For example, the following Anselme script:
--
-- ```
-- | Hi!
-- | My name's John.
-- ```
-- will return a text event containing two LuaTexts, the first containing the text "Hi!" and the second "My name's John.".
--
-- Usage:
-- ```lua
-- local event_type, event_data = run_state:step()
-- if event_type == "text" then
-- -- event_data is a TextEventData, i.e. a list of LuaText
-- for _, luatext in ipairs(event_data) do
-- -- luatext is a list of text parts { text = "text string", tags = { ... } }
-- for _, textpart in ipairs(luatext) do
-- write_text_part_with_color(textpart.text, textpart.tags.color)
-- end
-- write_text("\n") -- for example, if we want a newline between each text line
-- end
-- else
-- -- handle other event types...
-- end
-- ```
-- @title TextEventData
local TextEventData local TextEventData
TextEventData = class { TextEventData = class {
-- returns a list of TextEventData where the first element of each text of each TextEventData has the same value for the tag tag_name -- [1] = LuaText, ...
--- Returns a list of TextEventData where the first part of each LuaText of each TextEventData has the same value for the tag `tag_name`.
--
-- In other words, this groups all the LuaTexts contained in this TextEventData using the `tag_name` tag and returns a list containing these groups.
--
-- For example, with the following Anselme script:
-- ```
-- speaker: "John" #
-- | A
-- | B
-- speaker: "Lana" #
-- | C
-- speaker: "John" #
-- | D
-- ```
-- calling `text_event_data:group_by("speaker")` will return a list of three TextEventData:
-- * the first with the texts "A" and "B"; both with the tag `speaker="John"`
-- * the second with the text "C"; with the tag `speaker="Lana"`
-- * the last with the text "D"; wiith the tag `speaker="John"`
group_by = function(self, tag_name) group_by = function(self, tag_name)
local l = {} local l = {}
local current_group local current_group
local tag_key = to_anselme(tag_name) local tag_key = to_anselme(tag_name)
local last_value local last_value
for _, event in ipairs(self) do for _, luatext in ipairs(self) do
local list = event.list local list = luatext.raw.list
if #list > 0 then if #list > 0 then
local value = list[1][2]:get_strict(tag_key) local value = list[1][2]:get_strict(tag_key)
if (not current_group) or (last_value == nil and value) or (last_value and value == nil) or (last_value and value and last_value:hash() ~= value:hash()) then -- new group if (not current_group) or (last_value == nil and value) or (last_value and value == nil) or (last_value and value and last_value:hash() ~= value:hash()) then -- new group
@ -22,11 +107,11 @@ TextEventData = class {
table.insert(l, current_group) table.insert(l, current_group)
last_value = value last_value = value
end end
table.insert(current_group, event) -- add to current group table.insert(current_group, luatext) -- add to current group
end end
end end
return l return l
end, end
} }
local Text local Text
@ -68,6 +153,10 @@ Text = Runtime(Event) {
return ("| %s |"):format(table.concat(t, " ")) return ("| %s |"):format(table.concat(t, " "))
end, end,
to_lua = function(self, state)
return LuaText:new(self, state)
end,
-- autocall when used directly as a statement -- autocall when used directly as a statement
eval_statement = function(self, state) eval_statement = function(self, state)
return self:call(state, ArgumentTuple:new()) return self:call(state, ArgumentTuple:new())
@ -77,8 +166,8 @@ Text = Runtime(Event) {
build_event_data = function(self, state, event_buffer) build_event_data = function(self, state, event_buffer)
local l = TextEventData:new() local l = TextEventData:new()
for _, event in event_buffer:iter(state) do for _, text in event_buffer:iter(state) do
table.insert(l, event) table.insert(l, text:to_lua(state))
end end
return l return l
end, end,

View file

@ -13,25 +13,20 @@
-- local state = anselme.new() -- local state = anselme.new()
-- state:load_stdlib() -- state:load_stdlib()
-- --
-- -- read an anselme script file -- -- load an anselme script file in a new branch
-- local f = assert(io.open("script.ans"))
-- local script = anselme.parse(f:read("a"), "script.ans")
-- f:close()
--
-- -- load the script in a new branch
-- local run_state = state:branch() -- local run_state = state:branch()
-- run_state:run(script) -- run_state:run_file("script.ans")
-- --
-- -- run the script -- -- run the script
-- while run_state:active() do -- while run_state:active() do
-- local e, data = run_state:step() -- local e, data = run_state:step()
-- if e == "text" then -- if e == "text" then
-- for _, l in ipairs(data) do -- for _, l in ipairs(data) do
-- print(l:format(run_state)) -- print(l)
-- end -- end
-- elseif e == "choice" then -- elseif e == "choice" then
-- for i, l in ipairs(data) do -- for i, l in ipairs(data) do
-- print(("%s> %s"):format(i, l:format(run_state))) -- print(("%s> %s"):format(i, l))
-- end -- end
-- local choice = tonumber(io.read("l")) -- local choice = tonumber(io.read("l"))
-- data:choose(choice) -- data:choose(choice)

View file

@ -1,5 +1,6 @@
local expression_to_ast = require("anselme.parser.expression.to_ast") local expression_to_ast = require("anselme.parser.expression.to_ast")
local utf8 = utf8 or require("lua-utf8")
local ast = require("anselme.ast") local ast = require("anselme.ast")
local PartialScope, Block, Call, Identifier = ast.PartialScope, ast.Block, ast.Call, ast.Identifier local PartialScope, Block, Call, Identifier = ast.PartialScope, ast.Block, ast.Call, ast.Identifier

View file

@ -7,11 +7,11 @@ local tag_manager = require("anselme.state.tag_manager")
local event_manager = require("anselme.state.event_manager") local event_manager = require("anselme.state.event_manager")
local translation_manager = require("anselme.state.translation_manager") local translation_manager = require("anselme.state.translation_manager")
local persistent_manager = require("anselme.state.persistent_manager") local persistent_manager = require("anselme.state.persistent_manager")
local uuid = require("anselme.common").uuid
local parser = require("anselme.parser") local parser = require("anselme.parser")
local binser = require("anselme.lib.binser") local binser = require("anselme.lib.binser")
local assert0 = require("anselme.common").assert0 local common = require("anselme.common")
local operator_priority = require("anselme.common").operator_priority local uuid, assert0, operator_priority = common.uuid, common.assert0, common.operator_priority
local to_anselme = require("anselme.common.to_anselme")
local anselme local anselme
local Identifier, Return, Node local Identifier, Return, Node
@ -178,12 +178,13 @@ State = class {
--- Load a script in this branch. It will become the active script. --- Load a script in this branch. It will become the active script.
-- --
-- `code` is the code string or AST to run. If `code` is a string, `source` is the source name string to show in errors (optional). -- `code` is the code string or AST to run. If `code` is a string, `source` is the source name string to show in errors (optional).
-- `tags` is an optional Lua table; its content will be added to the tags for the duration of the script.
-- --
-- Note that this will only load the script; execution will only start by using the `:step` method. Will error if a script is already active in this State. -- Note that this will only load the script; execution will only start by using the `:step` method. Will error if a script is already active in this State.
run = function(self, code, source) run = function(self, code, source, tags)
assert(not self:active(), "a script is already active") assert(not self:active(), "a script is already active")
self._coroutine = coroutine.create(function() self._coroutine = coroutine.create(function()
local r = assert0(self:eval_local(code, source)) local r = assert0(self:eval_local(code, source, tags))
event_manager:complete_flush(self) event_manager:complete_flush(self)
if Return:is(r) then r = r.expression end if Return:is(r) then r = r.expression end
return "return", r return "return", r
@ -191,17 +192,19 @@ State = class {
end, end,
--- Same as `:run`, but read the code from a file. --- Same as `:run`, but read the code from a file.
-- `source` will be set as the file path. -- `source` will be set as the file path.
run_file = function(self, path) run_file = function(self, path, tags)
local f = assert(io.open(path, "r")) local f = assert(io.open(path, "r"))
local block = parser(f:read("a"), path) local block = parser(f:read("a"), path)
f:close() f:close()
return self:run(block) return self:run(block, nil, tags)
end, end,
--- When a script is active, will resume running it until the next event. --- When a script is active, will resume running it until the next event.
-- --
-- Will error if no script is active. -- Will error if no script is active.
-- --
-- Returns `event type string, event data`. -- Returns `event type string, event data`.
--
-- See the [events](#events) section for details on event data types for built-in events.
step = function(self) step = function(self)
assert(self:active(), "trying to step but no script is currently active") assert(self:active(), "trying to step but no script is currently active")
local success, type, data = coroutine.resume(self._coroutine) local success, type, data = coroutine.resume(self._coroutine)
@ -218,16 +221,17 @@ State = class {
-- --
-- Will error if no script is active. -- Will error if no script is active.
-- --
-- If `code` is given, the script will not be disabled but instead will be immediately replaced with this new script. -- `code`, `source` and `tags` are all optional and have the same behaviour as in `:run`.
-- If they are given, the script will not be disabled but instead will be immediately replaced with this new script.
-- The new script will then be started on the next `:step` and will preserve the current scope. This can be used to trigger an exit function or similar in the active script. -- The new script will then be started on the next `:step` and will preserve the current scope. This can be used to trigger an exit function or similar in the active script.
-- --
-- If this is called from within a running script, this will raise an `interrupt` event in order to stop the current script execution. -- If this is called from within a running script, this will raise an `interrupt` event in order to stop the current script execution.
interrupt = function(self, code, source) interrupt = function(self, code, source, tags)
assert(self:active(), "trying to interrupt but no script is currently active") assert(self:active(), "trying to interrupt but no script is currently active")
local called_from_script = self:state() == "running" local called_from_script = self:state() == "running"
if code then if code then
self._coroutine = coroutine.create(function() self._coroutine = coroutine.create(function()
local r = assert0(self:eval_local(code, source)) local r = assert0(self:eval_local(code, source, tags))
event_manager:complete_flush(self) event_manager:complete_flush(self)
self.scope:reset() -- scope stack is probably messed up after the switch self.scope:reset() -- scope stack is probably messed up after the switch
if Return:is(r) then r = r.expression end if Return:is(r) then r = r.expression end
@ -244,19 +248,24 @@ State = class {
-- --
-- This can be called from outside a running script, but an error will be triggered the expression raise any event other than return. -- This can be called from outside a running script, but an error will be triggered the expression raise any event other than return.
-- --
-- `code` is the code string or AST to run. If `code` is a string, `source` is the source name string to show in errors (optional).
-- `tags` is an optional Lua table; its content will be added to the tags for the duration of the expression.
--
-- * returns AST in case of success. Run `:to_lua(state)` on it to convert to a Lua value. -- * returns AST in case of success. Run `:to_lua(state)` on it to convert to a Lua value.
-- * returns `nil, error message` in case of error. -- * returns `nil, error message` in case of error.
eval = function(self, code, source) eval = function(self, code, source, tags)
self.scope:push_global() self.scope:push_global()
local r, e = self:eval_local(code, source) local r, e = self:eval_local(code, source, tags)
self.scope:pop() self.scope:pop()
return r, e return r, e
end, end,
--- Same as `:eval`, but evaluate the expression in the current scope. --- Same as `:eval`, but evaluate the expression in the current scope.
eval_local = function(self, code, source) eval_local = function(self, code, source, tags)
if type(code) == "string" then code = parser(code, source) end if type(code) == "string" then code = parser(code, source) end
local stack_size = self.scope:size() local stack_size = self.scope:size()
if tags then tag_manager:push(self, to_anselme(tags)) end
local s, e = pcall(code.eval, code, self) local s, e = pcall(code.eval, code, self)
if tags then tag_manager:pop(self) end
if not s then if not s then
self.scope:reset(stack_size) self.scope:reset(stack_size)
return nil, e return nil, e

View file

@ -56,7 +56,7 @@ return [[
:@arrondi = stdlib.round :@arrondi = stdlib.round
:@aléatoire = stdlib.rand :@aléatoire = stdlib.rand
:@égal = stdlib.equal :@est égal = stdlib.is equal
:@est = stdlib.is :@est = stdlib.is
:@est une ancre = stdlib.is anchor :@est une ancre = stdlib.is anchor
:@est un booléen = stdlib.is boolean :@est un booléen = stdlib.is boolean

View file

@ -1,6 +1,6 @@
This document describes how to use the main Anselme modules. This is generated automatically from the source files. This document describes how to use the main Anselme modules. This is generated automatically from the source files.
Note that this file only describes the `anselme` and `state.State` modules, which are only a selection of what I consider to be the "public API" of Anselme that I will try to keep stable. Note that this file only describes the `anselme` and `state.State` modules, as well as the `TextEventData` and `ChoiceEventData` classes, which are only a selection of what I consider to be the "public API" of Anselme that I will try to keep stable.
If you need more advanced control on Anselme, feel free to look into the other source files to find more; the most useful functions should all be reasonably commented. If you need more advanced control on Anselme, feel free to look into the other source files to find more; the most useful functions should all be reasonably commented.
# anselme # anselme
@ -15,25 +15,20 @@ local anselme = require("anselme")
local state = anselme.new() local state = anselme.new()
state:load_stdlib() state:load_stdlib()
-- read an anselme script file -- load an anselme script file in a new branch
local f = assert(io.open("script.ans"))
local script = anselme.parse(f:read("a"), "script.ans")
f:close()
-- load the script in a new branch
local run_state = state:branch() local run_state = state:branch()
run_state:run(script) run_state:run_file("script.ans")
-- run the script -- run the script
while run_state:active() do while run_state:active() do
local e, data = run_state:step() local e, data = run_state:step()
if e == "text" then if e == "text" then
for _, l in ipairs(data) do for _, l in ipairs(data) do
print(l:format(run_state)) print(l)
end end
elseif e == "choice" then elseif e == "choice" then
for i, l in ipairs(data) do for i, l in ipairs(data) do
print(("%s> %s"):format(i, l:format(run_state))) print(("%s> %s"):format(i, l))
end end
local choice = tonumber(io.read("l")) local choice = tonumber(io.read("l"))
data:choose(choice) data:choose(choice)
@ -56,31 +51,31 @@ Anselme expects that `require("anselme.module")` will try loading both `anselme/
Global version string. Follow semver. Global version string. Follow semver.
_defined at line 58 of [anselme/init.lua](../anselme/init.lua):_ `version = "2.0.0-rc1",` _defined at line 53 of [anselme/init.lua](../anselme/init.lua):_ `version = "2.0.0-rc1",`
### .versions ### .versions
Table containing per-category version numbers. Incremented by one for any change that may break compatibility. Table containing per-category version numbers. Incremented by one for any change that may break compatibility.
_defined at line 61 of [anselme/init.lua](../anselme/init.lua):_ `versions = {` _defined at line 56 of [anselme/init.lua](../anselme/init.lua):_ `versions = {`
#### .language #### .language
Version number for language and standard library changes. Version number for language and standard library changes.
_defined at line 63 of [anselme/init.lua](../anselme/init.lua):_ `language = 31,` _defined at line 58 of [anselme/init.lua](../anselme/init.lua):_ `language = 31,`
#### .save #### .save
Version number for save/AST format changes. Version number for save/AST format changes.
_defined at line 65 of [anselme/init.lua](../anselme/init.lua):_ `save = 7,` _defined at line 60 of [anselme/init.lua](../anselme/init.lua):_ `save = 7,`
#### .api #### .api
Version number for Lua API changes. Version number for Lua API changes.
_defined at line 67 of [anselme/init.lua](../anselme/init.lua):_ `api = 10` _defined at line 62 of [anselme/init.lua](../anselme/init.lua):_ `api = 10`
### .parse (code, source) ### .parse (code, source)
@ -94,14 +89,14 @@ local ast = anselme.parse("1 + 2", "test")
ast:eval(state) ast:eval(state)
``` ```
_defined at line 79 of [anselme/init.lua](../anselme/init.lua):_ `parse = function(code, source)` _defined at line 74 of [anselme/init.lua](../anselme/init.lua):_ `parse = function(code, source)`
### .parse_file (path) ### .parse_file (path)
Same as `:parse`, but reads the code from a file. Same as `:parse`, but reads the code from a file.
`source` will be set as the file path. `source` will be set as the file path.
_defined at line 84 of [anselme/init.lua](../anselme/init.lua):_ `parse_file = function(path)` _defined at line 79 of [anselme/init.lua](../anselme/init.lua):_ `parse_file = function(path)`
### .generate_translation_template (code, source) ### .generate_translation_template (code, source)
@ -109,20 +104,20 @@ Generates and return Anselme code (as a string) that can be used as a base for a
This will include every translatable element found in this code. This will include every translatable element found in this code.
`source` is an optional string; it will be used as the code source name in translation contexts. `source` is an optional string; it will be used as the code source name in translation contexts.
_defined at line 93 of [anselme/init.lua](../anselme/init.lua):_ `generate_translation_template = function(code, source)` _defined at line 88 of [anselme/init.lua](../anselme/init.lua):_ `generate_translation_template = function(code, source)`
### .generate_translation_template_file (path) ### .generate_translation_template_file (path)
Same as `:generate_translation_template`, but reads the code from a file. Same as `:generate_translation_template`, but reads the code from a file.
`source` will be set as the file path. `source` will be set as the file path.
_defined at line 98 of [anselme/init.lua](../anselme/init.lua):_ `generate_translation_template_file = function(path)` _defined at line 93 of [anselme/init.lua](../anselme/init.lua):_ `generate_translation_template_file = function(path)`
### .new () ### .new ()
Return a new [State](#state). Return a new [State](#state).
_defined at line 102 of [anselme/init.lua](../anselme/init.lua):_ `new = function()` _defined at line 97 of [anselme/init.lua](../anselme/init.lua):_ `new = function()`
# State # State
@ -249,22 +244,23 @@ Returns `"inactive"` if no script is loaded.
_defined at line 171 of [anselme/state/State.lua](../anselme/state/State.lua):_ `state = function(self)` _defined at line 171 of [anselme/state/State.lua](../anselme/state/State.lua):_ `state = function(self)`
### :run (code, source) ### :run (code, source, tags)
Load a script in this branch. It will become the active script. Load a script in this branch. It will become the active script.
`code` is the code string or AST to run. If `code` is a string, `source` is the source name string to show in errors (optional). `code` is the code string or AST to run. If `code` is a string, `source` is the source name string to show in errors (optional).
`tags` is an optional Lua table; its content will be added to the tags for the duration of the script.
Note that this will only load the script; execution will only start by using the `:step` method. Will error if a script is already active in this State. Note that this will only load the script; execution will only start by using the `:step` method. Will error if a script is already active in this State.
_defined at line 183 of [anselme/state/State.lua](../anselme/state/State.lua):_ `run = function(self, code, source)` _defined at line 184 of [anselme/state/State.lua](../anselme/state/State.lua):_ `run = function(self, code, source, tags)`
### :run_file (path) ### :run_file (path, tags)
Same as `:run`, but read the code from a file. Same as `:run`, but read the code from a file.
`source` will be set as the file path. `source` will be set as the file path.
_defined at line 194 of [anselme/state/State.lua](../anselme/state/State.lua):_ `run_file = function(self, path)` _defined at line 195 of [anselme/state/State.lua](../anselme/state/State.lua):_ `run_file = function(self, path, tags)`
### :step () ### :step ()
@ -274,42 +270,218 @@ Will error if no script is active.
Returns `event type string, event data`. Returns `event type string, event data`.
_defined at line 205 of [anselme/state/State.lua](../anselme/state/State.lua):_ `step = function(self)` See the [events](#events) section for details on event data types for built-in events.
### :interrupt (code, source) _defined at line 208 of [anselme/state/State.lua](../anselme/state/State.lua):_ `step = function(self)`
### :interrupt (code, source, tags)
Stops the currently active script. Stops the currently active script.
Will error if no script is active. Will error if no script is active.
If `code` is given, the script will not be disabled but instead will be immediately replaced with this new script. `code`, `source` and `tags` are all optional and have the same behaviour as in `:run`.
If they are given, the script will not be disabled but instead will be immediately replaced with this new script.
The new script will then be started on the next `:step` and will preserve the current scope. This can be used to trigger an exit function or similar in the active script. The new script will then be started on the next `:step` and will preserve the current scope. This can be used to trigger an exit function or similar in the active script.
If this is called from within a running script, this will raise an `interrupt` event in order to stop the current script execution. If this is called from within a running script, this will raise an `interrupt` event in order to stop the current script execution.
_defined at line 225 of [anselme/state/State.lua](../anselme/state/State.lua):_ `interrupt = function(self, code, source)` _defined at line 229 of [anselme/state/State.lua](../anselme/state/State.lua):_ `interrupt = function(self, code, source, tags)`
### :eval (code, source) ### :eval (code, source, tags)
Evaluate an expression in the global scope. Evaluate an expression in the global scope.
This can be called from outside a running script, but an error will be triggered the expression raise any event other than return. This can be called from outside a running script, but an error will be triggered the expression raise any event other than return.
`code` is the code string or AST to run. If `code` is a string, `source` is the source name string to show in errors (optional).
`tags` is an optional Lua table; its content will be added to the tags for the duration of the expression.
* returns AST in case of success. Run `:to_lua(state)` on it to convert to a Lua value. * returns AST in case of success. Run `:to_lua(state)` on it to convert to a Lua value.
* returns `nil, error message` in case of error. * returns `nil, error message` in case of error.
_defined at line 249 of [anselme/state/State.lua](../anselme/state/State.lua):_ `eval = function(self, code, source)` _defined at line 256 of [anselme/state/State.lua](../anselme/state/State.lua):_ `eval = function(self, code, source, tags)`
### :eval_local (code, source) ### :eval_local (code, source, tags)
Same as `:eval`, but evaluate the expression in the current scope. Same as `:eval`, but evaluate the expression in the current scope.
_defined at line 256 of [anselme/state/State.lua](../anselme/state/State.lua):_ `eval_local = function(self, code, source)` _defined at line 263 of [anselme/state/State.lua](../anselme/state/State.lua):_ `eval_local = function(self, code, source, tags)`
If you want to perform more advanced manipulation of the resulting AST nodes, look at the `ast` modules. If you want to perform more advanced manipulation of the resulting AST nodes, look at the `ast` modules.
In particular, every Node inherits the methods from [ast.abstract.Node](../ast/abstract/Node.lua). In particular, every Node inherits the methods from [ast.abstract.Node](../ast/abstract/Node.lua).
Otherwise, each Node has its own module file defined in the [ast/](../ast) directory. Otherwise, each Node has its own module file defined in the [ast/](../ast) directory.
# Events
Anselme scripts communicate with the game by sending events. See the [language documentation](language.md#events) for more details on events.
Custom events can be defined; to do so, simply yield the coroutine with your custom event type (using `coroutine.yield("event type", event_data)`) from a function called in the anselme script.
For example, to add a `wait` event that pauses the script for some time, you could do something along these lines:
```lua
state:define("wait", "(duration::is number)", function(duration) coroutine.yield("wait", duration) end)
waiting = false
-- and edit your Anselme event handler with something like:
if not waiting then
local event_type, event_data = run_state = run_state:step()
if e == "wait" then
waiting = true
call_after_duration(event_data, function() waiting = false end)
else
-- handle other event types...
end
end
```
And then from your Anselme script:
```
| Hello...
--- ---
_file generated at 2024-11-09T17:00:43Z_ wait(5)
| ...world !
```
## TextEventData
TextEventData represent the data returned by an event with the type `"text"`.
See the [language documentation](language.md#texts) for more details on how to create a text event.
A TextEventData contains a list of [LuaText](#luatext), each LuaText representing a separate line of the text event.
For example, the following Anselme script:
```
| Hi!
| My name's John.
```
will return a text event containing two LuaTexts, the first containing the text "Hi!" and the second "My name's John.".
Usage:
```lua
local event_type, event_data = run_state:step()
if event_type == "text" then
-- event_data is a TextEventData, i.e. a list of LuaText
for _, luatext in ipairs(event_data) do
-- luatext is a list of text parts { text = "text string", tags = { ... } }
for _, textpart in ipairs(luatext) do
write_text_part_with_color(textpart.text, textpart.tags.color)
end
write_text("\n") -- for example, if we want a newline between each text line
end
else
-- handle other event types...
end
```
_defined at line 74 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `local TextEventData`
### :group_by (tag_name)
Returns a list of TextEventData where the first part of each LuaText of each TextEventData has the same value for the tag `tag_name`.
In other words, this groups all the LuaTexts contained in this TextEventData using the `tag_name` tag and returns a list containing these groups.
For example, with the following Anselme script:
```
speaker: "John" #
| A
| B
speaker: "Lana" #
| C
speaker: "John" #
| D
```
calling `text_event_data:group_by("speaker")` will return a list of three TextEventData:
* the first with the texts "A" and "B"; both with the tag `speaker="John"`
* the second with the text "C"; with the tag `speaker="Lana"`
* the last with the text "D"; wiith the tag `speaker="John"`
_defined at line 96 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `group_by = function(self, tag_name)`
## ChoiceEventData
ChoiceEventData represent the data returned by an event with the type `"choice"`.
See the [language documentation](language.md#choices) for more details on how to create a choice event.
A ChoiceEventData contains a list of [LuaText](#luatext), each LuaText representing a separate choice of the choice event.
For example, the following Anselme script:
```
*| Yes!
*| No.
```
will return a choice event containing two LuaTexts, the first containing the text "Yes!" and the second "No.".
Usage:
```lua
current_choice = nil
waiting_for_choice = false
-- in your anselem event handling loop:
if not waiting_for_choice then
local event_type, event_data = run_state:step()
if event_type == "choice" then
-- event_data is a ChoiceEventData, i.e. a list of LuaText
for i, luatext in ipairs(event_data) do
write(("Choice number %s:"):format(i))
-- luatext is a list of text parts { text = "text string", tags = { ... } }
for _, textpart in ipairs(luatext) do
write_choice_part_with_color(textpart.text, textpart.tags.color)
end
else
-- handle other event types...
end
current_choice = event_data
waiting_for_choice = true
end
end
-- somewhere in your code where choices are selected
current_choice:select(choice_number)
waiting_for_choice = false
```
_defined at line 50 of [anselme/ast/Choice.lua](../anselme/ast/Choice.lua):_ `local ChoiceEventData = class {`
### :choose (choice)
Choose the choice at position `choice` (number).
A choice must be selected after receiving a choice event and before calling `:step` again.
_defined at line 58 of [anselme/ast/Choice.lua](../anselme/ast/Choice.lua):_ `choose = function(self, choice)`
## LuaText
A Lua-friendly representation of an Anselme Text value.
They appear in both TextEventData and ChoiceEventData to represent the text that has to be shown.
It contains a list of _text parts_, which are parts of a single text, each part potentially having differrent tags attached.
A text will typically only consist of a single part unless it was built using text interpolation.
Each text part is a table containing `text` (string) and `tags` (table) properties, for example: `{ text = "text part string", tags = { color = "red" } }`.
_defined at line 17 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `local LuaText`
### .raw
Anselme Text value this was created from. For advanced usage only. See the source file [Text.lua](anselme/ast/Text.lua) for more information.
_defined at line 25 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `raw = nil,`
### :__tostring ()
Returns a text representation of the LuaText, using Anselme's default formatting. Useful for debugging.
Usage: `print(luatext)`
_defined at line 39 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `__tostring = function(self)`
---
_file generated at 2024-11-11T13:33:43Z_

View file

@ -1,6 +1,6 @@
This document describes how to use the main Anselme modules. This is generated automatically from the source files. This document describes how to use the main Anselme modules. This is generated automatically from the source files.
Note that this file only describes the `anselme` and `state.State` modules, which are only a selection of what I consider to be the "public API" of Anselme that I will try to keep stable. Note that this file only describes the `anselme` and `state.State` modules, as well as the `TextEventData` and `ChoiceEventData` classes, which are only a selection of what I consider to be the "public API" of Anselme that I will try to keep stable.
If you need more advanced control on Anselme, feel free to look into the other source files to find more; the most useful functions should all be reasonably commented. If you need more advanced control on Anselme, feel free to look into the other source files to find more; the most useful functions should all be reasonably commented.
# anselme # anselme
@ -10,3 +10,40 @@ If you need more advanced control on Anselme, feel free to look into the other s
# State # State
{{anselme/state/State.lua}} {{anselme/state/State.lua}}
# Events
Anselme scripts communicate with the game by sending events. See the [language documentation](language.md#events) for more details on events.
Custom events can be defined; to do so, simply yield the coroutine with your custom event type (using `coroutine.yield("event type", event_data)`) from a function called in the anselme script.
For example, to add a `wait` event that pauses the script for some time, you could do something along these lines:
```lua
state:define("wait", "(duration::is number)", function(duration) coroutine.yield("wait", duration) end)
waiting = false
-- and edit your Anselme event handler with something like:
if not waiting then
local event_type, event_data = run_state = run_state:step()
if e == "wait" then
waiting = true
call_after_duration(event_data, function() waiting = false end)
else
-- handle other event types...
end
end
```
And then from your Anselme script:
```
| Hello...
---
wait(5)
| ...world !
```
{{anselme/ast/Text.lua}}
{{anselme/ast/Choice.lua}}
{{:lua text}}

View file

@ -887,6 +887,8 @@ A text event value can be created using the [text literal](#text). A text is wri
text! text!
``` ```
How the data returned by a Text event is structured is detailled in the [API documentation](api.md#texteventdata).
### Choices ### Choices
Choice events are intended to represent a player choice in the host game. Each choice event in the buffer list is intended to represent a distinct choice. Choice events are intended to represent a player choice in the host game. Each choice event in the buffer list is intended to represent a distinct choice.
@ -902,6 +904,8 @@ A choice event can be written to the buffer using the `*_` operator on a text ev
| Choice B has been selected. | Choice B has been selected.
``` ```
How the data returned by a Text event is structured is detailled in the [API documentation](api.md#choiceeventdata).
### Tags ### Tags
Text and choice events can also carry metadata through tags. Tags are stored as a [struct](#struct). Text and choice events can also carry metadata through tags. Tags are stored as a [struct](#struct).

View file

@ -38,14 +38,14 @@ local function run_loop(run_state, write_output, interactive)
for _, v in ipairs(grouped) do for _, v in ipairs(grouped) do
if groups then write_output(":: group ::") end if groups then write_output(":: group ::") end
for _, l in ipairs(v) do for _, l in ipairs(v) do
write_output(l:format(run_state)) write_output(l.raw:format(run_state))
end end
end end
elseif e == "choice" then elseif e == "choice" then
local choice local choice
if interactive then if interactive then
for i, l in ipairs(data) do for i, l in ipairs(data) do
write_output(("%s> %s"):format(i, l:format(run_state))) write_output(("%s> %s"):format(i, l.raw:format(run_state)))
end end
io.write(("Select choice (1-%s): "):format(#data)) io.write(("Select choice (1-%s): "):format(#data))
choice = tonumber(io.read("l")) choice = tonumber(io.read("l"))
@ -53,9 +53,9 @@ local function run_loop(run_state, write_output, interactive)
choice = assert(run_state:eval_local("choice"), "no choice selected"):to_lua() choice = assert(run_state:eval_local("choice"), "no choice selected"):to_lua()
for i, l in ipairs(data) do for i, l in ipairs(data) do
if i == choice then if i == choice then
write_output(("=> %s"):format(l:format(run_state))) write_output(("=> %s"):format(l.raw:format(run_state)))
else else
write_output((" > %s"):format(l:format(run_state))) write_output((" > %s"):format(l.raw:format(run_state)))
end end
end end
end end