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

Updated documentation; added Lua API documentation

This commit is contained in:
Étienne Fildadut 2022-01-16 23:37:35 +01:00
parent 933e8fb0ee
commit 69b9e17020
7 changed files with 566 additions and 129 deletions

6
API.md
View file

@ -1,6 +0,0 @@
Lua API reference
=================
We actively support LuaJIT and Lua 5.4. Lua 5.1, 5.2 and 5.3 *should* work but I don't always test against them.
TODO see anselme.lua, it is reasonably commentated and we should probably generate some documentation using LDoc.

View file

@ -442,9 +442,9 @@ Anselme need to give back control to the game at some point. This is done throug
Each event is composed of two elements: a type (string; `text`, `choice`, `return` or `error` by default, custom types can also be defined) and associated data; the data associated with each event depends on its type. For the default events this data is: Each event is composed of two elements: a type (string; `text`, `choice`, `return` or `error` by default, custom types can also be defined) and associated data; the data associated with each event depends on its type. For the default events this data is:
* `text` (text to display) is a list of text elements, each with a `text` field, containing the text contents, and a `tags` field, containing the tags associated with this text. * `text` (text to display) is a list of text elements, each with a `text` field, containing the text contents, and a `tags` field, containing the tags associated with this text.
* `choice` (choices to choose from) is a list of tableas, each associated to a choice. Each of these choice is a list of text elements like for the `text` event. * `choice` (choices to choose from) is a list of choices. Each of these choice is a list of text elements like for the `text` event.
* `return` (when the script ends) is the returned value. * `return` (when the script ends) is the returned value.
* `error` (when the script error) is the error message. * `error` (when there is an error) is the error message.
#### Event buffer #### Event buffer

View file

@ -1,4 +1,4 @@
Copyright 2019-2021 Étienne "Reuh" Fildadut Copyright 2019-2022 Étienne "Reuh" Fildadut
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

View file

@ -3,7 +3,7 @@ Anselme
The overengineered dialog scripting system in pure Lua. The overengineered dialog scripting system in pure Lua.
**Documentation and language are still WIP and will change. I am using this in a project and modify it as my needs change.** Whatever is on the master branch should work fine. **Documentation and language are still WIP and will change. I am using this in a project and modify it as my needs change.** Breaking changes are documented in commit messages.
Purpose Purpose
------- -------
@ -28,15 +28,16 @@ And most stuff you'd expect from such a language:
* can save and restore state * can save and restore state
And things that are halfway there but *should* be there eventually (i.e., TODO): And things that are halfway there but *should* be there eventually (i.e., TODO):
* language independant; scripts should (hopefully) be easily localizable into any language (it's possible, but doesn't provide any batteries for this right now)
Defaults variables use emoji and then it's expected to alias them; works but not the most satisfying solution. * language independant; scripts should (hopefully) be easily localizable into any language (it's possible, but doesn't provide any batteries for this right now).
* a good documentation Defaults variables use emoji and then it's expected to alias them; works but not the most satisfying solution.
Need to work on consistent naming of Anselme concepts * a good documentation (need to work on consistent naming of Anselme concepts, a step by step tutorial)
A step by step tutorial
Things that Anselme is not: Things that Anselme is not:
* a game engine. It's very specific to dialogs and text, so unless you make a text game you will need to do a lot of other stuff. * a game engine. It's very specific to dialogs and text, so unless you make a text game you will need to do a lot of other stuff.
* a language based on Lua. It's imperative and arrays start at 1 but there's not much else in common. * a language based on Lua. It's imperative and arrays start at 1 but there's not much else in common.
* a high-performance language. No, really, I didn't even try to make anything fast, so don't use Anselme to compute primes.
Example Example
------- -------
@ -70,4 +71,4 @@ Reference
See [LANGUAGE.md](LANGUAGE.md) for a reference of the language. See [LANGUAGE.md](LANGUAGE.md) for a reference of the language.
See [API.md](API.md) for the Lua API's documentation. See [anselme.md](anselme.md) for the Lua API's documentation.

View file

@ -1,17 +1,69 @@
-- anselme module --- anselme main module
--- Anselme Lua API reference
--
-- We actively support LuaJIT and Lua 5.4. Lua 5.1, 5.2 and 5.3 *should* work but I don't always test against them.
--
-- This documentation is generated from the main module file `anselme.lua` using `ldoc --ext md anselme.lua`.
--
-- Example usage:
-- ```lua
-- local anselme = require("anselme") -- load main module
--
-- local vm = anselme() -- create new VM
-- vm:loadgame("game") -- load some scripts, etc.
-- local interpreter = vm:rungame() -- create a new interpreter using what was loaded with :loadgame
--
-- -- simple function to convert text event data into a string
-- -- in your game you may want to handle tags, here we ignore them for simplicity
-- local function format_text(text)
-- local r = ""
-- for _, l in ipairs(t) do
-- r = r .. l.text
-- end
-- return r
-- end
--
-- -- event loop
-- repeat
-- local event, data = interpreter:step() -- progress script until next event
-- if event == "text" then
-- print(format_text(d))
-- elseif event == "choice" then
-- for j, choice in ipairs(d) do
-- print(j.."> "..format_text(choice))
-- end
-- interpreter:choose(io.read())
-- elseif event == "error" then
-- error(data)
-- end
-- until t == "return" or t == "error"
-- ```
--
-- Calling the Anselme main module will create a return a new [VM](#vms).
--
-- The main module also contain a few fields:
--
-- @type anselme
local anselme = { local anselme = {
--- version --- Anselme version information table.
-- save is incremented a each update which may break save compatibility --
-- language is incremented a each update which may break script file compatibility -- Contains version informations as number (higher means more recent) of Anselme divied in a few categories:
-- api is incremented a each update which may break Lua API compatibility --
-- * `save`, which is incremented at each update which may break save compatibility
-- * `language`, which is incremented at each update which may break script file compatibility
-- * `api`, which is incremented at each update which may break Lua API compatibility
versions = { versions = {
save = 1, save = 1,
language = 22, language = 22,
api = 5 api = 5
}, },
--- version is incremented at each update --- General version number.
--
-- It is incremented at each update.
version = 23, version = 23,
--- currently running interpreter --- Currently running [interpreter](#interpreters).
-- `nil` if no interpreter running.
running = nil running = nil
} }
package.loaded[...] = anselme package.loaded[...] = anselme
@ -62,19 +114,43 @@ local function is_file(path)
end end
end end
--- interpreter methods --- Interpreters
--
-- An interpreter is in charge of running Anselme code and is spawned from a [VM](#vms).
-- Several interpreters from the same VM can run at the same time.
--
-- Typically, you would have a interpreter for each script that need at the same time, for example one for every NPC
-- that is currently talking.
--
-- Each interpreter can only run one script at a time, and will run it sequentially.
-- You can advance in the script by calling the `:step` method, which will run the script until an event is sent (for example some text needs to be displayed),
-- which will pause the whole interpreter until `:step` is called again.
--
-- @type interpreter
local interpreter_methods = { local interpreter_methods = {
--- interpreter state --- interpreter state
-- for internal use, you shouldn't touch this -- for internal use, you shouldn't touch this
-- @local
state = nil, state = nil,
--- VM this interpreter belongs to --- [VM](#vms) this interpreter belongs to.
vm = nil, vm = nil,
--- event that stopped the interpreter --- String, type of the event that stopped the interpreter (`nil` if interpreter is still running).
end_event = nil, end_event = nil,
--- run the VM until the next event --- Run the interpreter until the next event.
-- will merge changed variables on successful script end -- Returns event type (string), data (any).
-- returns event, data; if event is "return" or "error", the interpreter can not be stepped further --
-- Will merge changed variables on successful script end.
--
-- If event is `"return"` or `"error"`, the interpreter can not be stepped further and should be discarded.
--
-- Default event types and their associated data:
-- * `text`: text to display, data is a list of text elements, each with a `text` field, containing the text contents, and a `tags` field, containing the tags associated with this text
-- * `choice`: choices to choose from, data is a list of choices Each of these choice is a list of text elements like for the `text` event
-- * `return`: when the script ends, data is the returned value (`nil` if nothing returned)
-- * `error`: when there is an error, data is the error message.
--
-- See [LANGUAGE.md](LANGUAGE.md) for more details on events.
step = function(self) step = function(self)
-- check status -- check status
if self.end_event then if self.end_event then
@ -110,21 +186,26 @@ local interpreter_methods = {
return event, data return event, data
end, end,
--- select an answer --- Select a choice.
-- returns self -- `i` is the index (number) of the choice in the choice list (from the choice event's data).
--
-- The choice will be selected on the next interpreter step.
--
-- Returns this interpreter.
choose = function(self, i) choose = function(self, i)
self.state.interpreter.choice_selected = tonumber(i) self.state.interpreter.choice_selected = tonumber(i)
return self return self
end, end,
--- interrupt the vm on the next step, executing an expression (if specified) in the current namespace --- Interrupt (abort the currently running script) the interpreter on the next step, executing an expression (string, if specified) in the current namespace instead.
-- returns self --
-- Returns this interpreter.
interrupt = function(self, expr) interrupt = function(self, expr)
self.state.interpreter.interrupt = expr or true self.state.interpreter.interrupt = expr or true
return self return self
end, end,
--- search closest namespace from last run line --- Returns the namespace (string) the last ran line belongs to.
current_namespace = function(self) current_namespace = function(self)
local line = self.state.interpreter.running_line local line = self.state.interpreter.running_line
local namespace = "" local namespace = ""
@ -141,9 +222,12 @@ local interpreter_methods = {
return namespace return namespace
end, end,
--- run an expression or block: may trigger events and must be called from within the interpreter coroutine --- Run an expression (string) or block, optionally in a specific namespace (string, will use root namespace if not specified).
-- no automatic merge if this change the interpreter state, merge is done once we reach end of script in a call to :step as usual -- This may trigger events and must be called from within the interpreter coroutine (i.e. from a function called from a running script).
-- return lua value (nil if nothing returned) --
-- No automatic merge if this change the interpreter state, merge is done once we reach end of script in a call to `:step` as usual.
--
-- Returns the returned value (nil if nothing returned).
run = function(self, expr, namespace) run = function(self, expr, namespace)
-- check status -- check status
if coroutine.status(self.state.interpreter.coroutine) ~= "running" then if coroutine.status(self.state.interpreter.coroutine) ~= "running" then
@ -168,12 +252,15 @@ local interpreter_methods = {
end end
return to_lua(r) return to_lua(r)
end, end,
--- evaluate an expression or block --- Evaluate an expression (string) or block, optionally in a specific namespace (string, will use root namespace if not specified).
-- can be called from outside the coroutine. Will create a new coroutine that operate on this interpreter state. -- The expression can't yield events.
-- no automatic merge if this change the interpreter state, merge is done once we reach end of script in a call to :step as usual -- Can be called from outside the interpreter coroutine. Will create a new coroutine that operate on this interpreter state.
-- the expression can't yield events --
-- return value in case of success (nil if nothing returned) -- No automatic merge if this change the interpreter state, merge is done once we reach end of script in a call to `:step` as usual.
-- return nil, err in case of error --
-- Returns the returned value in case of success (nil if nothing returned).
--
-- Returns nil, error message in case of error.
eval = function(self, expr, namespace) eval = function(self, expr, namespace)
if self.end_event then if self.end_event then
return "error", ("interpreter can't be restarted after receiving a %s event"):format(self.end_event) return "error", ("interpreter can't be restarted after receiving a %s event"):format(self.end_event)
@ -211,33 +298,43 @@ local interpreter_methods = {
} }
interpreter_methods.__index = interpreter_methods interpreter_methods.__index = interpreter_methods
--- vm methods --- VMs
--
-- A VM stores the state required to run Anselme scripts. Each VM is completely independant from each other.
--
-- @type vm
local vm_mt = { local vm_mt = {
--- anselme state --- anselme state
-- for internal use, you shouldn't touch this -- for internal use, you shouldn't touch this
-- @local
state = nil, state = nil,
--- loaded game state --- loaded game state
-- for internal use, you shouldn't touch this -- for internal use, you shouldn't touch this
-- @local
game = nil, game = nil,
--- wrapper for loading a whole set of scripts --- Wrapper for loading a whole set of scripts (a "game").
-- should be preferred to other loading functions if possible -- Should be preferred to other loading functions if possible as this sets all the common options on its own.
-- requires LÖVE or LuaFileSystem --
-- will load in path, in order: -- Requires LÖVE or LuaFileSystem.
-- * config.ans, which will be executed in the "config" namespace and may contains various optional configuration options: --
-- * anselme version: number, version of the anselme language this game was made for -- Will load from the directory given by `path` (string), in order:
-- * game version: any, version information of the game. Can be used to perform eventual migration of save with an old version in the main file. -- * `config.ans`, which will be executed in the "config" namespace and may contains various optional configuration options:
-- * `anselme version`: number, version of the anselme language this game was made for
-- * `game version`: any, version information of the game. Can be used to perform eventual migration of save with an old version in the main file.
-- Always included in saved variables. -- Always included in saved variables.
-- * language: string, built-in language file to load -- * `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 -- * `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 (see vm:setinjection) -- 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. -- * `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 -- * `start expression`: string, expression that will be ran when starting the game
-- * main file, if defined in config.ans -- * files in the global directory, if defined in config.ans
-- * every other file in the path and subdirectories, using their path as namespace (i.e., contents of path/world1/john.ans will be defined in a function world1.john) -- * every other file in the path and subdirectories, using their path as namespace (i.e., contents of path/world1/john.ans will be defined in a function world1.john)
-- returns self in case of success --
-- returns nil, err in case of error -- Returns this VM in case of success.
--
-- Returns nil, error message in case of error.
loadgame = function(self, path) loadgame = function(self, path)
if self.game then error("game already loaded") end if self.game then error("game already loaded") end
-- load config -- load config
@ -307,9 +404,11 @@ local vm_mt = {
end end
return self return self
end, end,
--- return a interpreter which runs the game main file --- Return a interpreter which runs the game start expression (if given).
-- return interpreter in case of success --
-- returns nil, err in case of error -- Returns interpreter in case of success.
--
-- Returns nil, error message in case of error.
rungame = function(self) rungame = function(self)
if not self.game then error("no game loaded") end if not self.game then error("no game loaded") end
if self.game.start_expression then if self.game.start_expression then
@ -319,16 +418,21 @@ local vm_mt = {
end end
end, end,
--- load code --- Load code from a string.
-- similar to Lua's code loading functions. -- Similar to Lua's code loading functions.
-- name(default=""): namespace to load the code in. Will define a new function is specified; otherwise, code will be parsed but not executable from an expression. --
-- return parsed block in case of success -- Compared to their Lua equivalents, these also take an optional `name` argument (default="") that set the namespace to load the code in. Will define a new function is specified; otherwise, code will be parsed but not executable from an expression (as it is not named).
-- returns nil, err in case of error --
-- Returns parsed block in case of success.
--
-- Returns nil, error message in case of error.
loadstring = function(self, str, name, source) loadstring = function(self, str, name, source)
local s, e = preparse(self.state, str, name or "", source) local s, e = preparse(self.state, str, name or "", source)
if not s then return s, e end if not s then return s, e end
return s return s
end, end,
--- Load code from a file.
-- See `vm:loadstring`.
loadfile = function(self, path, name) loadfile = function(self, path, name)
local content local content
if love then if love then
@ -345,10 +449,13 @@ local vm_mt = {
if not s then return s, err end if not s then return s, err end
return s return s
end, end,
-- load every file in a directory, using filename (without .ans extension) as its namespace -- Load every file in a directory, using filename (without .ans extension) as its namespace.
-- requires LÖVE or LuaFileSystem --
-- return self in case of success -- Requires LÖVE or LuaFileSystem.
-- returns nil, err in case of error --
-- Returns this VM in case of success.
--
-- Returns nil, error message in case of error.
loaddirectory = function(self, path, name) loaddirectory = function(self, path, name)
if not name then name = "" end if not name then name = "" end
name = name == "" and "" or name.."." name = name == "" and "" or name.."."
@ -367,42 +474,49 @@ local vm_mt = {
return self return self
end, end,
--- set aliases for built-in variables 👁️, 🔖 and 🏁 that will be defined on every new checkpoint and function --- Set aliases for built-in variables 👁️, 🔖 and 🏁 that will be defined on every new checkpoint and function.
-- this does not affect variables that were defined before this function was called -- This does not affect variables that were defined before this function was called.
-- nil for no alias -- Set to nil for no alias.
-- return self --
-- Returns this VM.
setaliases = function(self, seen, checkpoint, reached) setaliases = function(self, seen, checkpoint, reached)
self.state.builtin_aliases["👁️"] = seen self.state.builtin_aliases["👁️"] = seen
self.state.builtin_aliases["🔖"] = checkpoint self.state.builtin_aliases["🔖"] = checkpoint
self.state.builtin_aliases["🏁"] = reached self.state.builtin_aliases["🏁"] = reached
return self return self
end, end,
--- set some code that will be injected at specific places in all code loaded after this is called --- Set some code that will be injected at specific places in all code loaded after this is called.
-- possible inject types: -- Can typically be used to define variables for every function like 👁️, setting some value on every function resume, etc.
-- * "function start": injected at the start of every non-scoped function --
-- * "function end": injected at the end of every non-scoped function -- Possible inject types:
-- * "function return": injected at the end of each return's children that is contained in a non-scoped function -- * `"function start"`: injected at the start of every non-scoped function
-- * "checkpoint start": injected at the start of every checkpoint -- * `"function end"`: injected at the end of every non-scoped function
-- * "checkpoint end": injected at the end of every checkpoint -- * `"function return"`: injected at the end of each return's children that is contained in a non-scoped function
-- * "scoped function start": injected at the start of every scoped function -- * `"checkpoint start"`: injected at the start of every checkpoint
-- * "scoped function end": injected at the end of every scoped function -- * `"checkpoint end"`: injected at the end of every checkpoint
-- * "scoped function return": injected at the end of each return's children that is contained in a scoped function -- * `"scoped function start"`: injected at the start of every scoped function
-- set to nil to disable -- * `"scoped function end"`: injected at the end of every scoped function
-- can typically be used to define variables for every function like 👁️, setting some value on every function resume, etc. -- * `"scoped function return"`: injected at the end of each return's children that is contained in a scoped function
-- return self --
-- Set `code` to nil to disable the inject.
--
-- Returns this VM.
setinjection = function(self, inject, code) setinjection = function(self, inject, code)
assert(injections[inject], ("unknown injection type %q"):format(inject)) assert(injections[inject], ("unknown injection type %q"):format(inject))
self.state.inject[injections[inject]] = code self.state.inject[injections[inject]] = code
return self return self
end, end,
--- load & execute a built-in language file --- Load and execute a built-in language file.
-- the language file may optionally contain the special variables: --
-- The language file may optionally contain the special variables:
-- * alias 👁️: string, default alias for 👁️ -- * alias 👁️: string, default alias for 👁️
-- * alias 🏁: string, default alias for 🏁 -- * alias 🏁: string, default alias for 🏁
-- * alias 🔖: string, default alias for 🔖 -- * alias 🔖: string, default alias for 🔖
-- return self in case of success --
-- returns nil, err in case of error -- Returns this VM in case of success.
--
-- Returns nil, error message in case of error.
loadlanguage = function(self, lang) loadlanguage = function(self, lang)
local namespace = "anselme.languages."..lang local namespace = "anselme.languages."..lang
-- execute language file -- execute language file
@ -419,10 +533,12 @@ local vm_mt = {
return self return self
end, end,
--- define functions from Lua --- Define functions from Lua.
-- signature: full signature of the function --
-- fn: function (Lua function or table, see examples in stdlib/functions.lua) -- * `signature`: string, full signature of the function
-- return self -- * `fn`: function (Lua function or table, see examples in `stdlib/functions.lua`)
--
-- Returns this VM.
loadfunction = function(self, signature, fn) loadfunction = function(self, signature, fn)
if type(signature) == "table" then if type(signature) == "table" then
for k, v in pairs(signature) do for k, v in pairs(signature) do
@ -440,10 +556,13 @@ local vm_mt = {
return self return self
end, end,
--- save/load script state --- Save/load script state
-- only saves variables full names and values, so make sure to not change important variables, checkpoints and functions names between a save and a load --
-- only save variables with usable identifiers, so will skip functions with arguments, operators, etc. -- Only saves variables full names and values, so make sure to not change important variables, checkpoints and functions names between a save and a load.
-- loading should be after loading scripts (otherwise you will "variable already defined" errors) -- Also only save variables with usable identifiers, so will skip functions with arguments, operators, etc. (i.e. every scoped functions).
-- Loading should be done after loading all the game scripts (otherwise you will "variable already defined" errors).
--
-- Returns this VM.
load = function(self, data) load = function(self, data)
assert(anselme.versions.save == data.anselme.versions.save, ("trying to load data from an incompatible version of Anselme; save was done using save version %s but current version is %s"):format(data.anselme.versions.save, anselme.versions.save)) assert(anselme.versions.save == data.anselme.versions.save, ("trying to load data from an incompatible version of Anselme; save was done using save version %s but current version is %s"):format(data.anselme.versions.save, anselme.versions.save))
for k, v in pairs(data.variables) do for k, v in pairs(data.variables) do
@ -451,6 +570,10 @@ local vm_mt = {
end end
return self return self
end, end,
--- Save script state.
-- See `vm:load`.
--
-- Returns save data.
save = function(self) save = function(self)
local vars = {} local vars = {}
for k, v in pairs(self.state.variables) do for k, v in pairs(self.state.variables) do
@ -467,10 +590,12 @@ local vm_mt = {
} }
end, end,
--- perform parsing that needs to be done after loading code --- Perform parsing that needs to be done after loading code.
-- automatically ran before starting an interpreter, but you may want to execute it before if you want to check for parsing error manually -- This is automatically ran before starting an interpreter, but you may want to execute it before if you want to check for parsing error manually.
-- returns self in case of success --
-- returns nil, err in case of error -- Returns self in case of success.
--
-- Returns nil, error message in case of error
postload = function(self) postload = function(self)
if #self.state.queued_lines > 0 then if #self.state.queued_lines > 0 then
local r, e = postparse(self.state) local r, e = postparse(self.state)
@ -479,19 +604,20 @@ local vm_mt = {
return self return self
end, end,
--- enable feature flags --- Enable feature flags.
-- available flags: -- Available flags:
-- * "strip trailing spaces": remove trailing spaces from choice and text events (enabled by default) -- * `"strip trailing spaces"`: remove trailing spaces from choice and text events (enabled by default)
-- * "strip duplicate spaces": remove duplicated spaces between text elements from choice and text events (enabled by default) -- * `"strip duplicate spaces"`: remove duplicated spaces between text elements from choice and text events (enabled by default)
-- returns self --
-- Returns this VM.
enable = function(self, ...) enable = function(self, ...)
for _, flag in ipairs{...} do for _, flag in ipairs{...} do
self.state.feature_flags[flag] = true self.state.feature_flags[flag] = true
end end
return self return self
end, end,
--- disable features flags --- Disable features flags.
-- returns self -- Returns this VM.
disable = function(self, ...) disable = function(self, ...)
for _, flag in ipairs{...} do for _, flag in ipairs{...} do
self.state.feature_flags[flag] = nil self.state.feature_flags[flag] = nil
@ -499,13 +625,16 @@ local vm_mt = {
return self return self
end, end,
--- run code --- Run code.
-- expr: expression to evaluate (string or parsed expression), or a block to run -- Will merge state after successful execution
-- will merge state after successful execution --
-- namespace(default=""): namespace to evaluate the expression in -- * `expr`: expression to evaluate (string or parsed expression), or a block to run
-- tags(default={}): defaults tags when evaluating the expression (Lua value) -- * `namespace`(default=""): namespace to evaluate the expression in
-- return interpreter in case of success -- * `tags`(default={}): defaults tags when evaluating the expression (Lua value)
-- returns nil, err in case of error --
-- Return interpreter in case of success.
--
-- Returns nil, error message in case of error.
run = function(self, expr, namespace, tags) run = function(self, expr, namespace, tags)
local s, e = self:postload() local s, e = self:postload()
if not s then return s, e end if not s then return s, e end
@ -565,14 +694,17 @@ local vm_mt = {
} }
return setmetatable(interpreter, interpreter_methods) return setmetatable(interpreter, interpreter_methods)
end, end,
--- eval code --- Evaluate code.
-- behave like :run, except the expression can not emit events and will return the result of the expression directly. -- Behave like `:run`, except the expression can not emit events and will return the result of the expression directly.
-- merge state after sucessful execution automatically like :run -- Merge state after sucessful execution automatically like `:run`.
-- expr: expression to evaluate (string or parsed expression), or a block to evaluate --
-- namespace(default=""): namespace to evaluate the expression in -- * `expr`: expression to evaluate (string or parsed expression), or a block to evaluate
-- tags(default={}): defaults tags when evaluating the expression (Lua value) -- * `namespace`(default=""): namespace to evaluate the expression in
-- return value in case of success (nil if nothing returned) -- * `tags`(default={}): defaults tags when evaluating the expression (Lua value)
-- returns nil, err in case of error --
-- Return value in case of success (nil if nothing returned).
--
-- Returns nil, error message in case of error.
eval = function(self, expr, namespace, tags) eval = function(self, expr, namespace, tags)
local interpreter, err = self:run("()", namespace, tags) local interpreter, err = self:run("()", namespace, tags)
if not interpreter then return interpreter, err end if not interpreter then return interpreter, err end
@ -584,7 +716,7 @@ local vm_mt = {
} }
vm_mt.__index = vm_mt vm_mt.__index = vm_mt
--- anselme module -- return anselme module
return setmetatable(anselme, { return setmetatable(anselme, {
__call = function() __call = function()
-- global state -- global state

313
anselme.md Normal file
View file

@ -0,0 +1,313 @@
## Anselme Lua API reference
We actively support LuaJIT and Lua 5.4. Lua 5.1, 5.2 and 5.3 *should* work but I don't always test against them.
This documentation is generated from the main module file `anselme.lua` using `ldoc --ext md anselme.lua`.
Example usage:
```lua
local anselme = require("anselme") -- load main module
local vm = anselme() -- create new VM
vm:loadgame("game") -- load some scripts, etc.
local interpreter = vm:rungame() -- create a new interpreter using what was loaded with :loadgame
-- simple function to convert text event data into a string
-- in your game you may want to handle tags, here we ignore them for simplicity
local function format_text(text)
local r = ""
for _, l in ipairs(t) do
r = r .. l.text
end
return r
end
-- event loop
repeat
local event, data = interpreter:step() -- progress script until next event
if event == "text" then
print(format_text(d))
elseif event == "choice" then
for j, choice in ipairs(d) do
print(j.."> "..format_text(choice))
end
interpreter:choose(io.read())
elseif event == "error" then
error(data)
end
until t == "return" or t == "error"
```
Calling the Anselme main module will create a return a new [VM](#vms).
The main module also contain a few fields:
### anselme.versions
Anselme version information table.
Contains version informations as number (higher means more recent) of Anselme divied in a few categories:
* `save`, which is incremented at each update which may break save compatibility
* `language`, which is incremented at each update which may break script file compatibility
* `api`, which is incremented at each update which may break Lua API compatibility
### anselme.version
General version number.
It is incremented at each update.
### anselme.running
Currently running [interpreter](#interpreters).
`nil` if no interpreter running.
## Interpreters
An interpreter is in charge of running Anselme code and is spawned from a [VM](#vms).
Several interpreters from the same VM can run at the same time.
Typically, you would have a interpreter for each script that need at the same time, for example one for every NPC
that is currently talking.
Each interpreter can only run one script at a time, and will run it sequentially.
You can advance in the script by calling the `:step` method, which will run the script until an event is sent (for example some text needs to be displayed),
which will pause the whole interpreter until `:step` is called again.
### interpreter.vm
[VM](#vms) this interpreter belongs to.
### interpreter.end_event
String, type of the event that stopped the interpreter (`nil` if interpreter is still running).
### interpreter:step ()
Run the interpreter until the next event.
Returns event type (string), data (any).
Will merge changed variables on successful script end.
If event is `"return"` or `"error"`, the interpreter can not be stepped further and should be discarded.
Default event types and their associated data:
* `text`: text to display, data is a list of text elements, each with a `text` field, containing the text contents, and a `tags` field, containing the tags associated with this text
* `choice`: choices to choose from, data is a list of choices Each of these choice is a list of text elements like for the `text` event
* `return`: when the script ends, data is the returned value (`nil` if nothing returned)
* `error`: when there is an error, data is the error message.
See [LANGUAGE.md](LANGUAGE.md) for more details on events.
### interpreter:choose (i)
Select a choice.
`i` is the index (number) of the choice in the choice list (from the choice event's data).
The choice will be selected on the next interpreter step.
Returns this interpreter.
### interpreter:interrupt (expr)
Interrupt (abort the currently running script) the interpreter on the next step, executing an expression (string, if specified) in the current namespace instead.
Returns this interpreter.
### interpreter:current_namespace ()
Returns the namespace (string) the last ran line belongs to.
### interpreter:run (expr, namespace)
Run an expression (string) or block, optionally in a specific namespace (string, will use root namespace if not specified).
This may trigger events and must be called from within the interpreter coroutine (i.e. from a function called from a running script).
No automatic merge if this change the interpreter state, merge is done once we reach end of script in a call to `:step` as usual.
Returns the returned value (nil if nothing returned).
### interpreter:eval (expr, namespace)
Evaluate an expression (string) or block, optionally in a specific namespace (string, will use root namespace if not specified).
The expression can't yield events.
Can be called from outside the interpreter coroutine. Will create a new coroutine that operate on this interpreter state.
No automatic merge if this change the interpreter state, merge is done once we reach end of script in a call to `:step` as usual.
Returns the returned value in case of success (nil if nothing returned).
Returns nil, error message in case of error.
## VMs
A VM stores the state required to run Anselme scripts. Each VM is completely independant from each other.
### vm:loadgame (path)
Wrapper for loading a whole set of scripts (a "game").
Should be preferred to other loading functions if possible as this sets all the common options on its own.
Requires LÖVE or LuaFileSystem.
Will load from the directory given by `path` (string), in order:
* `config.ans`, which will be executed in the "config" namespace and may contains various optional configuration options:
* `anselme version`: number, version of the anselme language this game was made for
* `game version`: any, version information of the game. Can be used to perform eventual migration of save with an old version in the main file.
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 (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
* files in the global directory, if defined in config.ans
* every other file in the path and subdirectories, using their path as namespace (i.e., contents of path/world1/john.ans will be defined in a function world1.john)
Returns this VM in case of success.
Returns nil, error message in case of error.
### vm:rungame ()
Return a interpreter which runs the game start expression (if given).
Returns interpreter in case of success.
Returns nil, error message in case of error.
### vm:loadstring (str, name, source)
Load code from a string.
Similar to Lua's code loading functions.
Compared to their Lua equivalents, these also take an optional `name` argument (default="") that set the namespace to load the code in. Will define a new function is specified; otherwise, code will be parsed but not executable from an expression (as it is not named).
Returns parsed block in case of success.
Returns nil, error message in case of error.
### vm:loadfile (path, name)
Load code from a file.
See `vm:loadstring`.
### vm:setaliases (seen, checkpoint, reached)
Set aliases for built-in variables 👁️, 🔖 and 🏁 that will be defined on every new checkpoint and function.
This does not affect variables that were defined before this function was called.
Set to nil for no alias.
Returns this VM.
### vm:setinjection (inject, code)
Set some code that will be injected at specific places in all code loaded after this is called.
Can typically be used to define variables for every function like 👁️, setting some value on every function resume, etc.
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 `code` to nil to disable the inject.
Returns this VM.
### vm:loadlanguage (lang)
Load and execute a built-in language file.
The language file may optionally contain the special variables:
* alias 👁️: string, default alias for 👁️
* alias 🏁: string, default alias for 🏁
* alias 🔖: string, default alias for 🔖
Returns this VM in case of success.
Returns nil, error message in case of error.
### vm:loadfunction (signature, fn)
Define functions from Lua.
* `signature`: string, full signature of the function
* `fn`: function (Lua function or table, see examples in `stdlib/functions.lua`)
Returns this VM.
### vm:load (data)
Save/load script state
Only saves variables full names and values, so make sure to not change important variables, checkpoints and functions names between a save and a load.
Also only save variables with usable identifiers, so will skip functions with arguments, operators, etc. (i.e. every scoped functions).
Loading should be done after loading all the game scripts (otherwise you will "variable already defined" errors).
Returns this VM.
### vm:save ()
Save script state.
See `vm:load`.
Returns save data.
### vm:postload ()
Perform parsing that needs to be done after loading code.
This is automatically ran before starting an interpreter, but you may want to execute it before if you want to check for parsing error manually.
Returns self in case of success.
Returns nil, error message in case of error
### vm:enable (...)
Enable feature flags.
Available flags:
* `"strip trailing spaces"`: remove trailing spaces from choice and text events (enabled by default)
* `"strip duplicate spaces"`: remove duplicated spaces between text elements from choice and text events (enabled by default)
Returns this VM.
### vm:disable (...)
Disable features flags.
Returns this VM.
### vm:run (expr, namespace, tags)
Run code.
Will merge state after successful execution
* `expr`: expression to evaluate (string or parsed expression), or a block to run
* `namespace`(default=""): namespace to evaluate the expression in
* `tags`(default={}): defaults tags when evaluating the expression (Lua value)
Return interpreter in case of success.
Returns nil, error message in case of error.
### vm:eval (expr, namespace, tags)
Evaluate code.
Behave like `:run`, except the expression can not emit events and will return the result of the expression directly.
Merge state after sucessful execution automatically like `:run`.
* `expr`: expression to evaluate (string or parsed expression), or a block to evaluate
* `namespace`(default=""): namespace to evaluate the expression in
* `tags`(default={}): defaults tags when evaluating the expression (Lua value)
Return value in case of success (nil if nothing returned).
Returns nil, error message in case of error.

View file

@ -1,6 +1,3 @@
(FIXME compare with fe/janet types
$ f() $ f()
:a = 1 :a = 1