diff --git a/API.md b/API.md deleted file mode 100644 index 2828629..0000000 --- a/API.md +++ /dev/null @@ -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. diff --git a/LANGUAGE.md b/LANGUAGE.md index 2985245..aced0f5 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -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: * `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. -* `error` (when the script error) is the error message. +* `error` (when there is an error) is the error message. #### Event buffer diff --git a/LICENSE b/LICENSE index d17eaf6..07b4981 100644 --- a/LICENSE +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md index 5bc447a..ca5810d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Anselme 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 ------- @@ -28,15 +28,16 @@ And most stuff you'd expect from such a language: * can save and restore state 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. -* a good documentation - Need to work on consistent naming of Anselme concepts - A step by step tutorial + +* 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. +* a good documentation (need to work on consistent naming of Anselme concepts, a step by step tutorial) 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 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 ------- @@ -70,4 +71,4 @@ Reference 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. diff --git a/anselme.lua b/anselme.lua index 6128838..c4aab25 100644 --- a/anselme.lua +++ b/anselme.lua @@ -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 = { - --- version - -- save is incremented a each update which may break save compatibility - -- language is incremented a each update which may break script file compatibility - -- api is incremented a each update which may break Lua API compatibility + --- 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 versions = { save = 1, language = 22, api = 5 }, - --- version is incremented at each update + --- General version number. + -- + -- It is incremented at each update. version = 23, - --- currently running interpreter + --- Currently running [interpreter](#interpreters). + -- `nil` if no interpreter running. running = nil } package.loaded[...] = anselme @@ -62,19 +114,43 @@ local function is_file(path) 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 = { --- interpreter state -- for internal use, you shouldn't touch this + -- @local state = nil, - --- VM this interpreter belongs to + --- [VM](#vms) this interpreter belongs to. 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, - --- run the VM until the next event - -- will merge changed variables on successful script end - -- returns event, data; if event is "return" or "error", the interpreter can not be stepped further + --- 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. step = function(self) -- check status if self.end_event then @@ -110,21 +186,26 @@ local interpreter_methods = { return event, data end, - --- select an answer - -- returns self + --- 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. choose = function(self, i) self.state.interpreter.choice_selected = tonumber(i) return self end, - --- interrupt the vm on the next step, executing an expression (if specified) in the current namespace - -- returns self + --- 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. interrupt = function(self, expr) self.state.interpreter.interrupt = expr or true return self end, - --- search closest namespace from last run line + --- Returns the namespace (string) the last ran line belongs to. current_namespace = function(self) local line = self.state.interpreter.running_line local namespace = "" @@ -141,9 +222,12 @@ local interpreter_methods = { return namespace end, - --- run an expression or block: may trigger events and must be called from within the interpreter coroutine - -- 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 lua value (nil if nothing returned) + --- 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). run = function(self, expr, namespace) -- check status if coroutine.status(self.state.interpreter.coroutine) ~= "running" then @@ -168,12 +252,15 @@ local interpreter_methods = { end return to_lua(r) end, - --- evaluate an expression or block - -- can be called from outside the 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 - -- the expression can't yield events - -- return value in case of success (nil if nothing returned) - -- return nil, err in case of error + --- 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. eval = function(self, expr, namespace) if self.end_event then 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 ---- 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 = { --- anselme state -- for internal use, you shouldn't touch this + -- @local state = nil, --- loaded game state -- for internal use, you shouldn't touch this + -- @local game = nil, - --- wrapper for loading a whole set of scripts - -- should be preferred to other loading functions if possible - -- requires LÖVE or LuaFileSystem - -- will load in path, 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. + --- 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 + -- * `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 - -- * main file, if defined in config.ans + -- * `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 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) if self.game then error("game already loaded") end -- load config @@ -307,9 +404,11 @@ local vm_mt = { end return self end, - --- return a interpreter which runs the game main file - -- return interpreter in case of success - -- returns nil, err in case of error + --- 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. rungame = function(self) if not self.game then error("no game loaded") end if self.game.start_expression then @@ -319,16 +418,21 @@ local vm_mt = { end end, - --- load code - -- 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 - -- returns nil, err in case of error + --- 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. loadstring = function(self, str, name, source) local s, e = preparse(self.state, str, name or "", source) if not s then return s, e end return s end, + --- Load code from a file. + -- See `vm:loadstring`. loadfile = function(self, path, name) local content if love then @@ -345,10 +449,13 @@ local vm_mt = { if not s then return s, err end return s end, - -- 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 - -- returns nil, err in case of error + -- Load every file in a directory, using filename (without .ans extension) as its namespace. + -- + -- Requires LÖVE or LuaFileSystem. + -- + -- Returns this VM in case of success. + -- + -- Returns nil, error message in case of error. loaddirectory = function(self, path, name) if not name then name = "" end name = name == "" and "" or name.."." @@ -367,42 +474,49 @@ local vm_mt = { return self end, - --- 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 - -- nil for no alias - -- return self + --- 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. setaliases = function(self, seen, checkpoint, reached) self.state.builtin_aliases["👁️"] = seen self.state.builtin_aliases["🔖"] = checkpoint self.state.builtin_aliases["🏁"] = reached return self end, - --- 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 + --- 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. setinjection = function(self, inject, code) assert(injections[inject], ("unknown injection type %q"):format(inject)) self.state.inject[injections[inject]] = code return self end, - --- load & execute a built-in language file - -- the language file may optionally contain the special variables: + --- 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 🔖 - -- 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) local namespace = "anselme.languages."..lang -- execute language file @@ -419,10 +533,12 @@ local vm_mt = { return self end, - --- define functions from Lua - -- signature: full signature of the function - -- fn: function (Lua function or table, see examples in stdlib/functions.lua) - -- return self + --- 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. loadfunction = function(self, signature, fn) if type(signature) == "table" then for k, v in pairs(signature) do @@ -440,10 +556,13 @@ local vm_mt = { return self end, - --- 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. - -- loading should be after loading scripts (otherwise you will "variable already defined" errors) + --- 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. 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)) for k, v in pairs(data.variables) do @@ -451,6 +570,10 @@ local vm_mt = { end return self end, + --- Save script state. + -- See `vm:load`. + -- + -- Returns save data. save = function(self) local vars = {} for k, v in pairs(self.state.variables) do @@ -467,10 +590,12 @@ local vm_mt = { } end, - --- 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 - -- returns self in case of success - -- returns nil, err in case of error + --- 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 postload = function(self) if #self.state.queued_lines > 0 then local r, e = postparse(self.state) @@ -479,19 +604,20 @@ local vm_mt = { return self end, - --- 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 self + --- 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. enable = function(self, ...) for _, flag in ipairs{...} do self.state.feature_flags[flag] = true end return self end, - --- disable features flags - -- returns self + --- Disable features flags. + -- Returns this VM. disable = function(self, ...) for _, flag in ipairs{...} do self.state.feature_flags[flag] = nil @@ -499,13 +625,16 @@ local vm_mt = { return self end, - --- run code - -- expr: expression to evaluate (string or parsed expression), or a block to run - -- will merge state after successful execution - -- 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, err in case of error + --- 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. run = function(self, expr, namespace, tags) local s, e = self:postload() if not s then return s, e end @@ -565,14 +694,17 @@ local vm_mt = { } return setmetatable(interpreter, interpreter_methods) end, - --- eval 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, err in case of error + --- 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. eval = function(self, expr, namespace, tags) local interpreter, err = self:run("()", namespace, tags) if not interpreter then return interpreter, err end @@ -584,7 +716,7 @@ local vm_mt = { } vm_mt.__index = vm_mt ---- anselme module +-- return anselme module return setmetatable(anselme, { __call = function() -- global state diff --git a/anselme.md b/anselme.md new file mode 100644 index 0000000..e171608 --- /dev/null +++ b/anselme.md @@ -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. + diff --git a/test/tests/function scoped.ans b/test/tests/function scoped.ans index d358b84..c09d358 100644 --- a/test/tests/function scoped.ans +++ b/test/tests/function scoped.ans @@ -1,6 +1,3 @@ -(FIXME compare with fe/janet types - - $ f() :a = 1