mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Compare commits
No commits in common. "0aedf99a7887ca021bfaffc4590c61945722ed9e" and "77c6ac6ba29daaa4d7e6b7c5888baf11c6ff68a9" have entirely different histories.
0aedf99a78
...
77c6ac6ba2
28 changed files with 67 additions and 1659 deletions
|
|
@ -57,18 +57,7 @@ local ChoiceEventData = class {
|
|||
-- A choice must be selected after receiving a choice event and before calling `:step` again.
|
||||
choose = function(self, choice)
|
||||
self._selected = choice
|
||||
end,
|
||||
|
||||
-- Returns a simple table representation of this TextEventData.
|
||||
-- This contains no metatable, method, or cycle; only a list of simple representation of LuaText (see LuaText:to_simple_table).
|
||||
-- { lua_text_1_simple, lua_text_2_simple, ... }
|
||||
to_simple_table = function(self)
|
||||
local t = {}
|
||||
for _, lua_text in ipairs(self) do
|
||||
table.insert(t, lua_text:to_simple_table())
|
||||
end
|
||||
return t
|
||||
end,
|
||||
end
|
||||
}
|
||||
|
||||
local Choice
|
||||
|
|
@ -97,7 +86,7 @@ Choice = ast.abstract.Runtime(Event) {
|
|||
for _, c in event_buffer:iter(state) do
|
||||
table.insert(l, c.text:to_lua(state))
|
||||
end
|
||||
return { l }
|
||||
return l
|
||||
end,
|
||||
post_flush_callback = function(self, state, event_buffer, data)
|
||||
local choice = data._selected
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ local ArgumentTuple, Struct
|
|||
|
||||
local to_anselme = require("anselme.common.to_anselme")
|
||||
|
||||
local group_text_by_tag_identifier
|
||||
|
||||
--- 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.
|
||||
--
|
||||
|
|
@ -40,18 +38,7 @@ LuaText = class {
|
|||
-- @defer lua text
|
||||
__tostring = function(self)
|
||||
return self.raw:format(self._state)
|
||||
end,
|
||||
|
||||
-- Returns a simple table representation of this LuaText.
|
||||
-- This contains no metatable, method, or cycle; only the list part of this LuaText.
|
||||
-- { text = "string", tags = { tag_name = value, ... } }
|
||||
to_simple_table = function(self)
|
||||
local t = {}
|
||||
for _, part in ipairs(self) do
|
||||
table.insert(t, part)
|
||||
end
|
||||
return t
|
||||
end,
|
||||
end
|
||||
}
|
||||
|
||||
--- TextEventData represent the data returned by an event with the type `"text"`.
|
||||
|
|
@ -88,9 +75,9 @@ local TextEventData
|
|||
TextEventData = class {
|
||||
-- [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_key`.
|
||||
--- 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_key` tag and returns a list containing these groups.
|
||||
-- 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:
|
||||
-- ```
|
||||
|
|
@ -106,10 +93,10 @@ TextEventData = class {
|
|||
-- * 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_key)
|
||||
if type(tag_key) == "string" then tag_key = to_anselme(tag_key) end
|
||||
group_by = function(self, tag_name)
|
||||
local l = {}
|
||||
local current_group
|
||||
local tag_key = to_anselme(tag_name)
|
||||
local last_value
|
||||
for _, luatext in ipairs(self) do
|
||||
local list = luatext.raw.list
|
||||
|
|
@ -124,18 +111,7 @@ TextEventData = class {
|
|||
end
|
||||
end
|
||||
return l
|
||||
end,
|
||||
|
||||
-- Returns a simple table representation of this TextEventData.
|
||||
-- This contains no metatable, method, or cycle; only a list of simple representation of LuaText (see LuaText:to_simple_table).
|
||||
-- { lua_text_1_simple, lua_text_2_simple, ... }
|
||||
to_simple_table = function(self)
|
||||
local t = {}
|
||||
for _, lua_text in ipairs(self) do
|
||||
table.insert(t, lua_text:to_simple_table())
|
||||
end
|
||||
return t
|
||||
end,
|
||||
end
|
||||
}
|
||||
|
||||
local Text
|
||||
|
|
@ -193,17 +169,11 @@ Text = Runtime(Event) {
|
|||
for _, text in event_buffer:iter(state) do
|
||||
table.insert(l, text:to_lua(state))
|
||||
end
|
||||
if state.scope:defined(group_text_by_tag_identifier) then
|
||||
local tag_key = state.scope:get(group_text_by_tag_identifier)
|
||||
return l:group_by(tag_key)
|
||||
else
|
||||
return { l }
|
||||
end
|
||||
return l
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = Text
|
||||
ArgumentTuple, Struct = ast.ArgumentTuple, ast.Struct
|
||||
group_text_by_tag_identifier = ast.Identifier:new("_group_text_by_tag")
|
||||
|
||||
return Text
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ return ast.abstract.Node {
|
|||
type = "event",
|
||||
init = false,
|
||||
|
||||
-- returns list of values that will each be yielded in order by the whole event buffer data on flush
|
||||
-- returns value that will be yielded by the whole event buffer data on flush
|
||||
build_event_data = function(self, state, event_buffer)
|
||||
error("build_event_data not implemented for "..self.type)
|
||||
end,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
local class = require("anselme.lib.class")
|
||||
local fmt = require("anselme.common").fmt
|
||||
local binser = require("anselme.lib.binser")
|
||||
local utf8 = utf8 or (love and require("utf8") or require("lua-utf8"))
|
||||
local utf8 = utf8 or require("lua-utf8")
|
||||
local unpack = table.unpack or unpack
|
||||
|
||||
-- NODES SHOULD BE IMMUTABLE AFTER CREATION IF POSSIBLE!
|
||||
|
|
|
|||
|
|
@ -19,20 +19,20 @@
|
|||
--
|
||||
-- -- run the script
|
||||
-- while run_state:active() do
|
||||
-- local event, data = run_state:step()
|
||||
-- if event == "text" then
|
||||
-- local e, data = run_state:step()
|
||||
-- if e == "text" then
|
||||
-- for _, l in ipairs(data) do
|
||||
-- print(l)
|
||||
-- end
|
||||
-- elseif event == "choice" then
|
||||
-- elseif e == "choice" then
|
||||
-- for i, l in ipairs(data) do
|
||||
-- print(("%s> %s"):format(i, l))
|
||||
-- end
|
||||
-- local choice = tonumber(io.read("l"))
|
||||
-- data:choose(choice)
|
||||
-- elseif event == "return" then
|
||||
-- elseif e == "return" then
|
||||
-- run_state:merge()
|
||||
-- elseif event == "error" then
|
||||
-- elseif e == "error" then
|
||||
-- error(data)
|
||||
-- end
|
||||
-- end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
-- NOTE: Modified to serialize function upvalues.
|
||||
-- TODO: upstream
|
||||
|
||||
-- binser.lua
|
||||
|
||||
|
|
|
|||
|
|
@ -1,394 +0,0 @@
|
|||
-- NOTE: modified to add a json.null constant to allow encoding/decoding null values
|
||||
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
|
||||
json.null = {"json.null"}
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
if val == json.null then return "null" end
|
||||
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = json.null,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return json
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
local class = require("anselme.lib.class")
|
||||
local utf8 = utf8 or (love and require("utf8") or require("lua-utf8"))
|
||||
local utf8 = utf8 or require("lua-utf8")
|
||||
|
||||
local Source
|
||||
Source = class {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
local expression_to_ast = require("anselme.parser.expression.to_ast")
|
||||
|
||||
local utf8 = utf8 or (love and require("utf8") or require("lua-utf8"))
|
||||
local utf8 = utf8 or require("lua-utf8")
|
||||
local ast = require("anselme.ast")
|
||||
local PartialScope, Block, Call, Identifier = ast.PartialScope, ast.Block, ast.Call, ast.Identifier
|
||||
|
||||
|
|
|
|||
|
|
@ -1,184 +0,0 @@
|
|||
--- This is a Lua implementation of an Anselme client, with a nice API that mirrors the Anselme [State API](api.md#state) to communicate with the server.
|
||||
--
|
||||
-- Usage: create a Client object using the functions in the [anselme.server module](#anselme_server) and call `server:process()` regularly to process messages from the Server.
|
||||
--
|
||||
-- The API available here tries to follow the [State API](api.md#state) as much as possible, with the following differences:
|
||||
-- * functions that return a value in State take an additionnal argument `callback`:
|
||||
-- * if it is a function `callback(ret1, ret2, ...)`, it is called as soon as the return values `ret1, ret2, ...` are received. The function also returns the identifier `call_id` associated with the callback (to optionally cancel the callback later using `client:cancel(call_id)`).
|
||||
-- * if it is `nil`, return values are discarded;
|
||||
-- * if it is the string `"block"`, the call will block until the return values are received. The function returns these values directly.
|
||||
-- * functions that returns a `State` in State now returns a `Client`;
|
||||
-- * return values are converted to a simpler representation if possible (no metamethods, userdata or cycles) to make serialization simpler - in particular, Anselme values are automatically converted to Lua primitives.
|
||||
-- * a few new methods are introduced, see below.
|
||||
--
|
||||
-- Implementing a Client in other languages should be relatively easy: if your client language has a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) library, point it to the Anselme server you started using [`server.new_json_rpc_server()`](#new_json_rpc_server) and you're done.
|
||||
-- You should then be able to call any of the methods described in the [Server](#server).
|
||||
-- Additionnaly, if you plan to use the `define_rpc` or `define_local_rpc` server methods, you will need to implement the following remote method in your client that will be called by the server:
|
||||
-- * `call(function_id, ...)` where `function_id` (string) is the function identifier that was given when `define_rpc` or `define_local_rpc` was called, and `...` is a list of arguments. This must call the function associated with the `function_id` using the given arguments, and returns the values returned by the call (as a list of return values: `{ret1, ret2, ...}`).
|
||||
|
||||
local class = require("anselme.lib.class")
|
||||
local common = require("anselme.common")
|
||||
local uuid = common.uuid
|
||||
|
||||
local Client
|
||||
Client = class {
|
||||
rpc = nil,
|
||||
rpc_functions = nil,
|
||||
|
||||
-- `rpc` is the Rpc object to use to communicate with the Anselme server
|
||||
init = function(self, rpc, branch_from, branch_id)
|
||||
-- create a new branch from an existing server
|
||||
if branch_from then
|
||||
self.branch_id = branch_id
|
||||
self.source_branch = branch_from
|
||||
self.rpc = branch_from.rpc
|
||||
self.rpc_functions = branch_from.rpc_functions
|
||||
-- create new empty server
|
||||
else
|
||||
self.rpc_functions = {}
|
||||
self.rpc = rpc
|
||||
self.rpc.methods.call = function(func_id, ...)
|
||||
local fn = assert(self.rpc_functions[func_id])
|
||||
return fn(...)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
--- Process received messages.
|
||||
--
|
||||
-- Must be called regularly.
|
||||
-- If `block` is true, the function is allowed to block execution until a message is received.
|
||||
process = function(self, block)
|
||||
self.rpc:process(block)
|
||||
end,
|
||||
|
||||
--- Cancel the callback associated with the call `call_id`.
|
||||
-- This does not stop the remote method execution; only prevent the callback from being called.
|
||||
cancel = function(self, call_id)
|
||||
self.rpc:cancel(call_id)
|
||||
end,
|
||||
|
||||
--- If the last event was a `choice`, choose the `i`-th choice.
|
||||
-- This must be called before calling `:step` again after receiving a choice event.
|
||||
choose = function(self, i)
|
||||
self.rpc:call("choose", { self.branch_id, i })
|
||||
end,
|
||||
--- Remove the branch from the server.
|
||||
-- The branch (and therefore this Client branch) can't be used after calling this method.
|
||||
remove = function(self)
|
||||
self.rpc:call("remove", { self.branch_id })
|
||||
end,
|
||||
|
||||
--- Defines a function in the global scope, that calls the Lua function `func` on the Client when called.
|
||||
--
|
||||
-- The function will not be sent to the server; it will be directly executed on the client (i.e. your game code)
|
||||
-- each time a script on the server needs it to be called.
|
||||
--
|
||||
-- Usage: `client:define_rpc("teleport", "(position)", function(position) player:teleport(position) end)`
|
||||
define_rpc = function(self, name, args, func)
|
||||
local func_id = uuid()
|
||||
self.rpc_functions[func_id] = func
|
||||
self.rpc:call("define_rpc", { self.branch_id, name, args, func_id })
|
||||
return func_id
|
||||
end,
|
||||
--- Same as `:define_rpc`, but define the function in the current scope.
|
||||
define_local_rpc = function(self, name, args, func)
|
||||
local func_id = uuid()
|
||||
self.rpc_functions[func_id] = func
|
||||
self.rpc:call("define_local_rpc", { self.branch_id, name, args, func_id })
|
||||
return args
|
||||
end,
|
||||
|
||||
--- ## Methods and fields that mirror the State API
|
||||
|
||||
--- Same as [`state:load_stdlib(language)`](api.md#load_stdlib-language).
|
||||
load_stdlib = function(self, language)
|
||||
self.rpc:call("load_stdlib", { self.branch_id, language })
|
||||
end,
|
||||
|
||||
--- Same as [`state.branch_id`](api.md#branch_id).
|
||||
branch_id = "main",
|
||||
--- Same as [`state.source_branch`](api.md#source_branch), but refers to the source `Client` instead of a `State`.
|
||||
source_branch = nil,
|
||||
--- Same as [`state:branch(branch_id)`](api.md#branch-branch_id), but returns a new `Client` instead of a `State`.
|
||||
branch = function(self, branch_id, callback)
|
||||
local branch_id
|
||||
if callback == "block" then
|
||||
return Client:new(self.rpc, self, self.rpc:call("branch", { self.branch_id, branch_id }, callback))
|
||||
else
|
||||
return self.rpc:call("branch", { self.branch_id, branch_id }, function(id) callback(Client:new(self.rpc, self, id)) end)
|
||||
end
|
||||
end,
|
||||
--- Same as [`state:merge()`](api.md#merge).
|
||||
merge = function(self)
|
||||
self.rpc:call("merge", { self.branch_id })
|
||||
end,
|
||||
|
||||
--- Same as [`state:define(name, value, func, raw_mode)`](api.md#api.md#define-name-value-func-raw_mode), but if `func_code` is given, it must be a string containing the function code.
|
||||
--
|
||||
-- Note that the given code will be executed on the server, and that there is no sandboxing of any kind;
|
||||
--
|
||||
-- Example: `client:define("main", "print", "(message::is string)", "function(message) print(message) end")`.
|
||||
define = function(self, name, value, func_code, raw_mode)
|
||||
self.rpc:call("define", { self.branch_id, name, value, func_code, raw_mode })
|
||||
end,
|
||||
--- Same as [`define`](#define-name-value-func_code-raw_mode), but calls [`state:define_local(name, value, func, raw_mode)`](api.md#api.md#define_local-name-value-func-raw_mode).
|
||||
define_local = function(self, name, value, func_code, raw_mode)
|
||||
self.rpc:call("define_local", { self.branch_id, name, value, func_code, raw_mode })
|
||||
end,
|
||||
--- Same as [`state:defined(name)`](api.md#defined-name).
|
||||
defined = function(self, name, callback)
|
||||
return self.rpc:call("defined", { self.branch_id, name }, callback)
|
||||
end,
|
||||
--- Same as [`state:defined_local(name)`](api.md#defined_local-name).
|
||||
defined_local = function(self, name, callback)
|
||||
return self.rpc:call("defined_local", { self.branch_id, name }, callback)
|
||||
end,
|
||||
|
||||
--- Same as [`state:save()`](api.md#save).
|
||||
save = function(self, callback)
|
||||
return self.rpc:call("save", { self.branch_id }, callback)
|
||||
end,
|
||||
--- Same as [`state:load(save)`](api.md#load-save).
|
||||
load = function(self, save)
|
||||
self.rpc:call("load", { self.branch_id, save })
|
||||
end,
|
||||
|
||||
--- Same as [`state:active()`](api.md#active).
|
||||
active = function(self, callback)
|
||||
return self.rpc:call("active", { self.branch_id }, callback)
|
||||
end,
|
||||
--- Same as [`state:state()`](api.md#state).
|
||||
state = function(self, callback)
|
||||
return self.rpc:call("state", { self.branch_id }, callback)
|
||||
end,
|
||||
--- Same as [`state:run(code, source, tags)`](api.md#run-code-source-tags).
|
||||
run = function(self, code, source, tags)
|
||||
self.rpc:call("run", { self.branch_id, code, source, tags })
|
||||
end,
|
||||
--- Same as [`state:run_file(code, source, tags)`](api.md#run_file-code-source-tags).
|
||||
run_file = function(self, path, tags)
|
||||
self.rpc:call("run_file", { self.branch_id, path, tags })
|
||||
end,
|
||||
--- Same as [`state:step)`](api.md#step), but returns:
|
||||
-- * for `text` and `choice` events, a list of lines `{ { { text = "line 1 part 2", tags = { ... } }, ... }, ... }` (in other word, [`TextEventData`](api.md#texteventdata) and [`ChoiceEventData`](api.md#choiceeventdata) stripped of everything but their list of text parts);
|
||||
-- * for `return` events, the return value converted to Lua primitives;
|
||||
-- * for other events, it will try to return the event data as-is.
|
||||
step = function(self, callback)
|
||||
return self.rpc:call("step", { self.branch_id }, callback)
|
||||
end,
|
||||
--- Same as [`state:interrupt(code, source, tags)`](api.md#interrupt-code-source-tags).
|
||||
interrupt = function(self, code, source, tags)
|
||||
self.rpc:call("interrupt", { self.branch_id, code, source, tags })
|
||||
end,
|
||||
--- Same as [`state:eval(code, source, tags)`](api.md#eval-code-source-tags), but the returned value is converted to Lua primitives.
|
||||
eval = function(self, code, source, tags, callback)
|
||||
return self.rpc:call("eval", { self.branch_id, code, source, tags }, callback)
|
||||
end,
|
||||
--- Same as [`state:eval_local(code, source, tags)`](api.md#eval_local-code-source-tags), but the returned value is converted to Lua primitives.
|
||||
eval_local = function(self, code, source, tags, callback)
|
||||
return self.rpc:call("eval", { self.branch_id, code, source, tags }, callback)
|
||||
end,
|
||||
}
|
||||
|
||||
return Client
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
--- An Anselme server instance.
|
||||
--
|
||||
-- Usage: create a Server object using the functions in the [anselme.server module](#anselme_server) and call `server:process()` regularly to process messages from the Client.
|
||||
--
|
||||
-- If you are implementing your own client, the following methods are available to be remotely called by your client:
|
||||
-- * Note:
|
||||
-- * in all the following methods, the first parameter `branch_id` (string) is the id of the Anselme branch to operate on;
|
||||
-- * methods that return something always returns a list of return values: `{ ret1, ret2, ... }`.
|
||||
-- * `choose(branch_id, i)`: if the last event was a `choice`, choose the `i`-th (number) line in the choice list;
|
||||
-- * `remove(branch_id)`: removes the branch from the server; no further operation will be possible on the branch;
|
||||
-- * `load_stdlib(branch_id, language)`: calls [`state:load_stdlib(language)`](api.md#load_stdlib-language) on the branch;
|
||||
-- * `branch(branch_id[, new_branch_id])`: calls [`state:branch(branch_id)`](api.md#branch-branch_id) on the branch; returns the id of the new branch (string);
|
||||
-- * `merge(branch_id)`: calls [`state:merge()`](api.md#merge) on the branch;
|
||||
-- * `define(branch_id, name, args, func_code, raw_mode)`: calls [`state:define(branch_id, name, args, func, raw_mode)`](api.md#define-name-value-func-raw_mode) on the branch; if `func_code` is given, `func` will be a function generated from the Lua code `func_code` (string, example: `define("main", "print", "(message::is string)", "function(message) print(message) end")`). Note that whatever is in `func_code` will be executed on the server, and that there is no sandboxing of any kind;
|
||||
-- * `define_rpc(branch_id, name, args, func_id)`: defines a function in the branch that, when called, will call the remote method `call(func_id, ...)` on the client and block until it returns. In other words, this allows the Anselme script running on the server to transparently call the function that is associated with the id `func_id` on the client.
|
||||
-- * `define_local(branch_id, name, args, func_code, raw_mode)`: same as `define`, but calls [`state:define_local(branch_id, name, args, func, raw_mode)`](api.md#define_local-name-value-func-raw_mode);
|
||||
-- * `define_local_rpc(branch_id, name, args, func_id)`: same as `define_rpc`, but defines the function in the current scope;
|
||||
-- * `defined(branch_id, name)`: calls [`state:defined(name)`](api.md#defined-name) on the branch and returns its result;
|
||||
-- * `defined_local(branch_id, name)`: calls [`state:defined_local(name)`](api.md#defined_local-name) on the branch and returns its result;
|
||||
-- * `save(branch_id)`: calls [`state:save()`](api.md#save) on the branch and returns its result;
|
||||
-- * `load(branch_id, save)`: calls [`state:load(save)`](api.md#load-save) on the branch;
|
||||
-- * `active(branch_id)`: calls [`state:active()`](api.md#active) on the branch and returns its result;
|
||||
-- * `state(branch_id)`: calls [`state:state()`](api.md#state) on the branch and returns its result;
|
||||
-- * `run(branch_id, code, source, tags)`: calls [`state:run(code, source, tags)`](api.md#run-code-source-tags) on the branch;
|
||||
-- * `run_file(branch_id, path, tags)`: calls [`state:run_file(path, tags)`](api.md#run_file-path-tags) on the branch;
|
||||
-- * `step(branch_id)`: calls [`state:step()`](api.md#step) on the branch and returns:
|
||||
-- * for `text` and `choices` events, a list of lines `{ { { text = "line 1 part 2", tags = { ... } }, ... }, ... }` (in other word, [`TextEventData`](api.md#texteventdata) and [`ChoiceEventData`](api.md#choiceeventdata) stripped of everything but their list of text parts);
|
||||
-- * for `return` events, the return value converted to Lua;
|
||||
-- * for other events, it will try to return the event data as-is.
|
||||
-- * `interrupt(branch_id, code, source, tags)`: calls [`state:interrupt(code, source, tags)`](api.md#interrupt-code-source-tags) on the branch;
|
||||
-- * `eval(branch_id, code, source, tags)`: calls [`state:eval(code, source, tags)`](api.md#eval-code-source-tags) on the branch and returns its result, converted to Lua;
|
||||
-- * `eval_local(branch_id, code, source, tags)`: calls [`state:eval_local(code, source, tags)`](api.md#eval_local-code-source-tags) on the branch and returns its result, converted to Lua.
|
||||
|
||||
local class = require("anselme.lib.class")
|
||||
local anselme = require("anselme")
|
||||
|
||||
local Server
|
||||
Server = class {
|
||||
rpc = nil,
|
||||
branches = nil,
|
||||
|
||||
-- `rpc` is the Rpc object to use to communicate with the Anselme client
|
||||
init = function(self, rpc)
|
||||
local branches = {
|
||||
main = {
|
||||
choice = nil,
|
||||
state = anselme.new()
|
||||
}
|
||||
}
|
||||
|
||||
local methods = {
|
||||
choose = function(branch, i)
|
||||
branch.choice:choose(i)
|
||||
end,
|
||||
remove = function(branch)
|
||||
branches[branch.state.branch_id] = nil
|
||||
end,
|
||||
|
||||
load_stdlib = function(branch, language)
|
||||
branch.state:load_stdlib(language)
|
||||
end,
|
||||
|
||||
branch = function(branch, new_branch_id)
|
||||
local new_branch = branch.state:branch(new_branch_id)
|
||||
branches[new_branch.branch_id] = {
|
||||
choice = nil,
|
||||
state = new_branch
|
||||
}
|
||||
return new_branch.branch_id
|
||||
end,
|
||||
merge = function(branch)
|
||||
branch.state:merge()
|
||||
end,
|
||||
|
||||
define = function(branch, name, args, func_code, raw_mode)
|
||||
if func_code then func_code = assert(load("return "..func_code))() end
|
||||
branch.state:define(name, args, func_code, raw_mode)
|
||||
end,
|
||||
define_rpc = function(branch, name, args, func_id)
|
||||
branch.state:define(name, args, function(...)
|
||||
return rpc:call("call", { func_id, ... }, "block")
|
||||
end)
|
||||
end,
|
||||
define_local = function(branch, name, args, func_code, raw_mode)
|
||||
if func_code then func_code = assert(load("return "..func_code))() end
|
||||
branch.state:define_local(name, args, func_code, raw_mode)
|
||||
end,
|
||||
define_local_rpc = function(branch, name, args, func_id)
|
||||
branch.state:define_local(name, args, function(...)
|
||||
return rpc:call("call", { func_id, ... }, "block")
|
||||
end)
|
||||
end,
|
||||
defined = function(branch, name)
|
||||
return branch.state:defined(name)
|
||||
end,
|
||||
defined_local = function(branch, name)
|
||||
return branch.state:defined_local(name)
|
||||
end,
|
||||
|
||||
save = function(branch)
|
||||
return branch.state:save()
|
||||
end,
|
||||
load = function(branch, save)
|
||||
branch.state:load(save)
|
||||
end,
|
||||
|
||||
active = function(branch)
|
||||
return branch.state:active()
|
||||
end,
|
||||
state = function(branch)
|
||||
return branch.state:state()
|
||||
end,
|
||||
run = function(branch, code, source, tags)
|
||||
branch.state:run(code, source, tags)
|
||||
end,
|
||||
run_file = function(branch, path, tags)
|
||||
branch.state:run_file(path, tags)
|
||||
end,
|
||||
step = function(branch)
|
||||
local event, data = branch.state:step()
|
||||
if event == "text" then
|
||||
return "text", data:to_simple_table()
|
||||
elseif event == "choice" then
|
||||
branch.choice = data
|
||||
return "choice", data:to_simple_table()
|
||||
elseif event == "return" then
|
||||
return "return", data:to_lua(branch.state)
|
||||
elseif event == "error" then
|
||||
return "error", data
|
||||
else
|
||||
return event, data
|
||||
end
|
||||
end,
|
||||
interrupt = function(branch, code, source, tags)
|
||||
branch.state:interrupt(code, source, tags)
|
||||
end,
|
||||
eval = function(branch, code, source, tags)
|
||||
return branch.state:eval(code, source, tags):to_lua(branch.state)
|
||||
end,
|
||||
eval_local = function(branch, code, source, tags)
|
||||
return branch.state:eval_local(code, source, tags):to_lua(branch.state)
|
||||
end,
|
||||
}
|
||||
|
||||
for method, fn in pairs(methods) do
|
||||
rpc.methods[method] = function(branch_id, ...)
|
||||
local branch = assert(branches[branch_id], ("can't find branch %s"):format(branch_id))
|
||||
return fn(branch, ...)
|
||||
end
|
||||
end
|
||||
|
||||
self.rpc = rpc
|
||||
self.branches = branches
|
||||
end,
|
||||
|
||||
--- Process received messages.
|
||||
--
|
||||
-- Must be called regularly.
|
||||
-- If `block` is true, the function is allowed to block execution until a message is received.
|
||||
process = function(self, block)
|
||||
self.rpc:process(block)
|
||||
end,
|
||||
}
|
||||
|
||||
return Server
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
--- Main functions to create clients and servers.
|
||||
|
||||
local Client = require("anselme.server.Client")
|
||||
|
||||
local server
|
||||
server = {
|
||||
--- Starts a Server in a new LÖVE thread and returns a Client connected to that server.
|
||||
--
|
||||
-- Should be called from a [LÖVE](https://www.love2d.org/) game code only.
|
||||
new_love_thread = function()
|
||||
local LoveThread = require("anselme.server.rpc.LoveThread")
|
||||
local input = love.thread.newChannel()
|
||||
local output = love.thread.newChannel()
|
||||
|
||||
local thread = love.thread.newThread[[
|
||||
local path, input_channel, output_channel = ...
|
||||
package.path = path
|
||||
|
||||
local LoveThread = require("anselme.server.rpc.LoveThread")
|
||||
local Server = require("anselme.server.Server")
|
||||
|
||||
local rpc = LoveThread:new(input_channel, output_channel)
|
||||
local server = Server:new(rpc)
|
||||
|
||||
while true do
|
||||
server:process(true)
|
||||
end
|
||||
]]
|
||||
thread:start(package.path, input, output)
|
||||
|
||||
return Client:new(LoveThread:new(output, input))
|
||||
end,
|
||||
|
||||
--- Returns a new Server that communicate with a Client using JSON-RPC 2.0.
|
||||
--
|
||||
-- This does not define _how_ the two comminicate (through sockets, http, etc.), you will need to define this using the `send` and `receive` arguments.
|
||||
--
|
||||
-- `send(message)` is a function that send a single message to the associated Client.
|
||||
--
|
||||
-- `receive(block)` is a function that receive a single message from the associated Client (or `nil` if no message available). If `block` is true, the function is allowed to block execution until a message is received.
|
||||
new_json_rpc_server = function(send, receive)
|
||||
local Server = require("anselme.server.Server")
|
||||
local JsonRpc = require("anselme.server.rpc.JsonRpc")
|
||||
return Server:new(JsonRpc:new(send, receive))
|
||||
end,
|
||||
--- Returns a new Client that communicate with a Server using JSON-RPC 2.0.
|
||||
--
|
||||
-- This does not define _how_ the two comminicate (through sockets, http, etc.), you will need to define this using the `send` and `receive` arguments.
|
||||
--
|
||||
-- `send(message)` is a function that send a single message to the associated Server.
|
||||
--
|
||||
-- `receive(block)` is a function that receive a single message from the associated Server (or `nil` if no message available). If `block` is true, the function is allowed to block execution until a message is received.
|
||||
new_json_rpc_client = function(send, receive)
|
||||
local Client = require("anselme.server.Client")
|
||||
local JsonRpc = require("anselme.server.rpc.JsonRpc")
|
||||
return Client:new(JsonRpc:new(send, receive))
|
||||
end,
|
||||
}
|
||||
|
||||
return server
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
--- Communicate over JSON-RPC 2.0.
|
||||
--
|
||||
-- You will need to implement your own `_send` and `_receive` methods to send the message over your wanted communication channel (socket, stdio, etc.).
|
||||
|
||||
local Rpc = require("anselme.server.rpc.abstract.Rpc")
|
||||
local json = require("anselme.lib.json")
|
||||
|
||||
local JsonRpc = Rpc {
|
||||
_send = nil,
|
||||
_receive = nil,
|
||||
|
||||
send = function(self, data)
|
||||
if data.error and data.error.id == nil then
|
||||
data.error.id = json.null
|
||||
end
|
||||
data.jsonrpc = "2.0"
|
||||
self._send(json.encode(data))
|
||||
end,
|
||||
|
||||
receive = function(self, block)
|
||||
return json.decode(self._receive(block))
|
||||
end,
|
||||
|
||||
-- `send(message)` is a function that send a single message to the other party
|
||||
-- `receive(block)` is a function that receive a single message from the other party (or nil if no message available). If `block` is true, the function is allowed to block execution until a message is received.
|
||||
init = function(self, send, receive)
|
||||
Rpc.init(self)
|
||||
self._send = send
|
||||
self._receive = receive
|
||||
end
|
||||
}
|
||||
|
||||
return JsonRpc
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
--- Communicate over LÖVE threads using Channels.
|
||||
|
||||
local Rpc = require("anselme.server.rpc.abstract.Rpc")
|
||||
|
||||
local LoveThread = Rpc {
|
||||
_output = nil,
|
||||
_input = nil,
|
||||
|
||||
send = function(self, data)
|
||||
self._output:push(data)
|
||||
end,
|
||||
|
||||
receive = function(self, block)
|
||||
if block then
|
||||
return self._input:demand()
|
||||
else
|
||||
return self._input:pop()
|
||||
end
|
||||
end,
|
||||
|
||||
-- `input` is the LÖVE thread Channel used to send data from the Anselme server to the game engine
|
||||
-- `output` is the LÖVE thread Channel used to send data from the game engine to the Anselme server
|
||||
init = function(self, input, output)
|
||||
Rpc.init(self)
|
||||
self._input = input
|
||||
self._output = output
|
||||
end
|
||||
}
|
||||
|
||||
return LoveThread
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
-- Note: this does not support multiple clients for a single server.
|
||||
|
||||
local class = require("anselme.lib.class")
|
||||
|
||||
local function default_callback() end
|
||||
local function default_error_callback(message, data)
|
||||
if data then
|
||||
error(("in rpc call: %s\n%s"):format(message, data))
|
||||
else
|
||||
error(("in rpc call: %s"):format(message))
|
||||
end
|
||||
end
|
||||
|
||||
local Rpc = class {
|
||||
--- The message exchanged are Lua table representing [JSON-RPC 2.0](https://www.jsonrpc.org/specification) Request and Response objects, with the following caveats:
|
||||
--
|
||||
-- * by-name parameters are not supported in requests;
|
||||
-- * result values in responses are always arrays (corresponding to the return list of a Lua function);
|
||||
-- * each side act both as both a client and a server.
|
||||
--
|
||||
-- These should not break compatility with any JSON-RPC 2.0 compliant service, just keep them in mind :)
|
||||
--
|
||||
-- Note however that the messages generated by this file require a couple change to be truly compliant:
|
||||
--
|
||||
-- * in error response caused by parsing errors, `id` will be unset instead of Null (since Lua considers nil values to be inexistent);
|
||||
-- * the `jsonrpc="2.0"` field isn't set;
|
||||
-- * and the message should be encoded/decoded to/from JSON obviously.
|
||||
--
|
||||
-- These are meant to be handled in the `:send` and `:receive` methods. See JsonRpc.lua.
|
||||
--
|
||||
-- Alternatively, look at LoveThread.lua if you don't care about full compliance.
|
||||
|
||||
--- Send a message.
|
||||
--
|
||||
-- `data` must be a table reprensenting a JSON-RPC 2.0 message (with the considerations noted above).
|
||||
--
|
||||
-- Must be redefined to handle whatever inter-process comminication you are using.
|
||||
send = function(self, data) error("not implemented") end,
|
||||
--- Receives a message.
|
||||
--
|
||||
-- Must be non-blocking by default; may be optionnaly blocking if `block` if true. May raise errors.
|
||||
--
|
||||
-- Returns a table reprensenting a JSON RPC-2.0 message (see above) or `nil` if nothing to receive.
|
||||
--
|
||||
-- Must be redefined to handle whatever inter-process comminication you are using.
|
||||
receive = function(self, block) error("not implemented") end,
|
||||
|
||||
--- Table of methods that can be called remotely:
|
||||
-- {
|
||||
-- ["method1"] = function(param1, param2, ...)
|
||||
-- return ret1, ret2, ...
|
||||
-- -- may raise an error
|
||||
-- end,
|
||||
-- ["method2"] = ...,
|
||||
-- ...
|
||||
-- }
|
||||
methods = nil,
|
||||
|
||||
--- Returns a new Rpc object.
|
||||
init = function(self)
|
||||
self.methods = {}
|
||||
self._callbacks = {}
|
||||
self._error_callbacks = {}
|
||||
end,
|
||||
|
||||
_callbacks = nil, -- { [call id] = callback, ... }
|
||||
_error_callbacks = nil, -- { [call id] = error_callback, ... }
|
||||
id = 0, -- last used call id
|
||||
|
||||
--- Call a remote method.
|
||||
--
|
||||
-- Parameters:
|
||||
-- * `method` (string) is the method name.
|
||||
-- * `params` (table) is the parameter list.
|
||||
-- * `callback` (function, optional) is either:
|
||||
-- * a function that will be called when the method returns. It receives all the returned values as arguments (ret1, ret2, ...). If not set, a default callback that discard all returns values will be used
|
||||
-- * the string "block", in which case the function will block until the remote method returns.
|
||||
-- * `error_callback` (function, optional) is a function that will be called if the method raise an error. It receives the error message and error details (usually the traceback; may be nil) as arguments (message, traceback). If not set, a default callback that raise an error will be used.
|
||||
--
|
||||
-- Returns:
|
||||
-- * the call id (number) if `callback` is not `"block"`
|
||||
-- * the values returned by the remote method if `callback` is `"block"`
|
||||
call = function(self, method, params, callback, error_callback)
|
||||
self.id = self.id + 1
|
||||
self:send{ method = method, params = params, id = self.id }
|
||||
if callback == "block" then
|
||||
local ok, response
|
||||
self._callbacks[self.id] = function(...) ok, response = true, { ... } end
|
||||
self._error_callbacks[self.id] = error_callback and function(...) ok, response = true, {}; error_callback(...) end or default_error_callback
|
||||
while not ok do self:process() end
|
||||
return unpack(response)
|
||||
else
|
||||
self._callbacks[self.id] = callback or default_callback
|
||||
self._error_callbacks[self.id] = error_callback or default_error_callback
|
||||
return self.id
|
||||
end
|
||||
end,
|
||||
--- Same as `:call`, but always discards all returned values and errors.
|
||||
-- NOTE unused for now
|
||||
notify = function(self, method, params)
|
||||
self:send{ method = method, params = params }
|
||||
end,
|
||||
--- Cancel callbacks associated with the call `call_id` (number).
|
||||
-- This does not stop the remote method execution.
|
||||
cancel = function(self, call_id)
|
||||
self._callbacks[call_id] = default_callback
|
||||
self._error_callbacks[call_id] = default_error_callback
|
||||
end,
|
||||
|
||||
--- Process incoming message.
|
||||
-- This should be called regularly.
|
||||
-- If `block` is true, block execution until a message is received.
|
||||
process = function(self, block)
|
||||
local s, d = pcall(self.receive, self, block)
|
||||
if not s then
|
||||
self:send{ error = { code = -32700, message = "Parse error", data = d, id = nil } }
|
||||
elseif d then
|
||||
if type(d) ~= "table" then
|
||||
self:send{ error = { code = -32600, message = "Invalid Request", id = nil } }
|
||||
else
|
||||
-- request
|
||||
if d.method then
|
||||
self:send(self:_process_request(d))
|
||||
-- response
|
||||
elseif d.result or d.error then
|
||||
self:_process_response(d)
|
||||
-- batch
|
||||
elseif #d > 0 then
|
||||
local first = d[1]
|
||||
-- request batch
|
||||
if d.method then
|
||||
local responses = {}
|
||||
for _, req in ipairs(d) do
|
||||
table.insert(responses, self:_process_request(req))
|
||||
end
|
||||
if #responses > 1 then
|
||||
self:send(responses)
|
||||
end
|
||||
-- response batch
|
||||
else
|
||||
for _, res in ipairs(d) do
|
||||
self:_process_response(res)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
--- Process a request or notification.
|
||||
-- Returns a response for requests.
|
||||
-- Returns nothing for notifications.
|
||||
_process_request = function(self, d)
|
||||
local method, params, id = d.method, d.params or {}, d.id
|
||||
if type(method) ~= "string" or type(d.params) ~= "table" then
|
||||
self:send{ error = { code = -32600, message = "Invalid Request" }, id = id }
|
||||
return
|
||||
end
|
||||
local fn = self.methods[method]
|
||||
if not fn then
|
||||
if id then self:send{ error = { code = -32601, message = ("Method not found %s"):format(method) }, id = id } end
|
||||
return
|
||||
end
|
||||
if params[1] == nil and not next(params) then
|
||||
if id then self:send{ error = { code = -32602, message = "Named parameters are not supported" }, id = id } end
|
||||
return
|
||||
end
|
||||
local r = { xpcall(fn, function(err) return { err, debug.traceback("Traceback from RPC:", 2) } end, unpack(params)) }
|
||||
if id then
|
||||
if r[1] then
|
||||
return { result = { unpack(r, 2) }, id = id }
|
||||
else
|
||||
return { error = { code = 0, message = r[2][1], data = r[2][2] }, id = id }
|
||||
end
|
||||
end
|
||||
end,
|
||||
--- Process a response.
|
||||
_process_response = function(self, d)
|
||||
local result, err, id = d.result, d.error, d.id
|
||||
if id then
|
||||
assert(self._callbacks[id], "invalid response call id")
|
||||
if err then
|
||||
assert(type(err) == "table", "error must be a table")
|
||||
self._error_callbacks[id](tostring(err.message), err.data)
|
||||
else
|
||||
assert(type(result) == "table", "result must be a table")
|
||||
self._callbacks[id](unpack(result))
|
||||
end
|
||||
self._callbacks[id] = nil
|
||||
self._error_callbacks[id] = nil
|
||||
else
|
||||
default_error_callback(tostring(err.message), err.data)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return Rpc
|
||||
|
|
@ -74,7 +74,7 @@ State = class {
|
|||
|
||||
--- Name of the branch associated to this State.
|
||||
branch_id = "main",
|
||||
--- State this State was branched from. `nil` if this is the main branch.
|
||||
--- State this State was branched from.
|
||||
source_branch = nil,
|
||||
|
||||
--- Return a new branch of this State.
|
||||
|
|
@ -159,7 +159,7 @@ State = class {
|
|||
-- Currently active script
|
||||
_coroutine = nil,
|
||||
|
||||
--- Returns true if a script is currently loaded in this branch, false otherwise.
|
||||
--- Indicate if a script is currently loaded in this branch.
|
||||
active = function(self)
|
||||
return not not self._coroutine
|
||||
end,
|
||||
|
|
|
|||
|
|
@ -65,16 +65,13 @@ return class {
|
|||
if last_type then
|
||||
local last_buffer = state.scope:get(event_buffer_identifier)
|
||||
local event_president = last_buffer:get(state, 1) -- elected representative of all concerned events
|
||||
-- build event data list
|
||||
-- yield event data
|
||||
local data = event_president:build_event_data(state, last_buffer)
|
||||
coroutine.yield(last_type, data)
|
||||
-- clear room for the future
|
||||
self:reset(state)
|
||||
for _, event_data in ipairs(data) do
|
||||
-- yield event data
|
||||
coroutine.yield(last_type, event_data)
|
||||
-- post callback
|
||||
if event_president.post_flush_callback then event_president:post_flush_callback(state, last_buffer, event_data) end
|
||||
end
|
||||
-- post callback
|
||||
if event_president.post_flush_callback then event_president:post_flush_callback(state, last_buffer, data) end
|
||||
end
|
||||
end,
|
||||
-- keep flushing until nothing is left (a flush may re-fill the buffer during its execution)
|
||||
|
|
|
|||
|
|
@ -16,20 +16,7 @@ return {
|
|||
if l:truthy() then
|
||||
return Boolean:new(env:defined(state, s:to_identifier()))
|
||||
else
|
||||
return Boolean:new(env:defined_in_current(state, s:to_symbol()))
|
||||
end
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns true if the variable named `var` is defined in in the current scope, false otherwise.
|
||||
--
|
||||
-- If `search parent` is true, this will also search in parent scopes of the current scope.
|
||||
"defined", "(var::is string, search parent::is boolean=true)",
|
||||
function(state, s, l)
|
||||
if l:truthy() then
|
||||
return Boolean:new(state.scope:defined(s:to_identifier()))
|
||||
else
|
||||
return Boolean:new(state.scope:defined_in_current(s:to_symbol()))
|
||||
return Boolean:new(env:defined_in_current(state, s:to_identifier()))
|
||||
end
|
||||
end
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
-- TODO: doc in other language
|
||||
|
||||
return [[
|
||||
:@format = stdlib.format
|
||||
|
||||
:@bloc attaché = stdlib.attached block
|
||||
|
||||
:@afficher = stdlib.print
|
||||
|
|
@ -85,6 +83,4 @@ return [[
|
|||
:@persister = stdlib.persist
|
||||
|
||||
:@écrire choix = stdlib.write choice
|
||||
|
||||
:@grouper texte par tag = stdlib.group text by tag
|
||||
]]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
--- # Strings
|
||||
-- @titlelevel 3
|
||||
|
||||
local utf8 = utf8 or (love and require("utf8") or require("lua-utf8"))
|
||||
local utf8 = utf8 or require("lua-utf8")
|
||||
local ast = require("anselme.ast")
|
||||
local String, Number = ast.String, ast.Number
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ local translation_manager = require("anselme.state.translation_manager")
|
|||
local tag_manager = require("anselme.state.tag_manager")
|
||||
local resume_manager = require("anselme.state.resume_manager")
|
||||
|
||||
local group_text_by_tag_identifier = Identifier:new("_group_text_by_tag")
|
||||
local group_text_by_tag_symbol = group_text_by_tag_identifier:to_symbol { exported = true }
|
||||
|
||||
return {
|
||||
-- text
|
||||
{
|
||||
|
|
@ -44,37 +41,6 @@ return {
|
|||
end
|
||||
},
|
||||
|
||||
{
|
||||
--- Cause future text events to be each split into separate text event every time the value of the tag with the key `tag_key` changes.
|
||||
--
|
||||
-- For example, with the following Anselme script:
|
||||
-- ```
|
||||
-- group text by tag("speaker")
|
||||
-- speaker: "John" #
|
||||
-- | A
|
||||
-- | B
|
||||
-- speaker: "Lana" #
|
||||
-- | C
|
||||
-- speaker: "John" #
|
||||
-- | D
|
||||
-- ```
|
||||
-- will produce three separate text events instead of one:
|
||||
-- * 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"`
|
||||
--
|
||||
-- This setting affect will affect the whole state.
|
||||
"group text by tag", "(tag::is string)",
|
||||
function(state, tag)
|
||||
if not state.scope:defined(group_text_by_tag_identifier) then
|
||||
state.scope:define(group_text_by_tag_symbol, tag)
|
||||
else
|
||||
state.scope:set(group_text_by_tag_identifier, tag)
|
||||
end
|
||||
return Nil:new()
|
||||
end
|
||||
},
|
||||
|
||||
-- choice
|
||||
{
|
||||
--- Write a choice event to the event buffer using this text and `fn` as the function to call if the choice is selected.
|
||||
|
|
|
|||
32
doc/api.md
32
doc/api.md
|
|
@ -21,20 +21,20 @@ run_state:run_file("script.ans")
|
|||
|
||||
-- run the script
|
||||
while run_state:active() do
|
||||
local event, data = run_state:step()
|
||||
if event == "text" then
|
||||
local e, data = run_state:step()
|
||||
if e == "text" then
|
||||
for _, l in ipairs(data) do
|
||||
print(l)
|
||||
end
|
||||
elseif event == "choice" then
|
||||
elseif e == "choice" then
|
||||
for i, l in ipairs(data) do
|
||||
print(("%s> %s"):format(i, l))
|
||||
end
|
||||
local choice = tonumber(io.read("l"))
|
||||
data:choose(choice)
|
||||
elseif event == "return" then
|
||||
elseif e == "return" then
|
||||
run_state:merge()
|
||||
elseif event == "error" then
|
||||
elseif e == "error" then
|
||||
error(data)
|
||||
end
|
||||
end
|
||||
|
|
@ -146,7 +146,7 @@ _defined at line 76 of [anselme/state/State.lua](../anselme/state/State.lua):_ `
|
|||
|
||||
### .source_branch
|
||||
|
||||
State this State was branched from. `nil` if this is the main branch.
|
||||
State this State was branched from.
|
||||
|
||||
_defined at line 78 of [anselme/state/State.lua](../anselme/state/State.lua):_ `source_branch = nil,`
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ _defined at line 148 of [anselme/state/State.lua](../anselme/state/State.lua):_
|
|||
|
||||
### :active ()
|
||||
|
||||
Returns true if a script is currently loaded in this branch, false otherwise.
|
||||
Indicate if a script is currently loaded in this branch.
|
||||
|
||||
_defined at line 163 of [anselme/state/State.lua](../anselme/state/State.lua):_ `active = function(self)`
|
||||
|
||||
|
|
@ -376,13 +376,13 @@ else
|
|||
end
|
||||
```
|
||||
|
||||
_defined at line 87 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `local TextEventData`
|
||||
_defined at line 74 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `local TextEventData`
|
||||
|
||||
### :group_by (tag_key)
|
||||
### :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_key`.
|
||||
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_key` tag and returns a list containing these groups.
|
||||
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:
|
||||
```
|
||||
|
|
@ -399,7 +399,7 @@ calling `text_event_data:group_by("speaker")` will return a list of three TextEv
|
|||
* 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 109 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `group_by = function(self, tag_key)`
|
||||
_defined at line 96 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `group_by = function(self, tag_name)`
|
||||
|
||||
|
||||
## ChoiceEventData
|
||||
|
|
@ -467,13 +467,13 @@ A text will typically only consist of a single part unless it was built using te
|
|||
|
||||
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 19 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `local LuaText`
|
||||
_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 27 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `raw = nil,`
|
||||
_defined at line 25 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `raw = nil,`
|
||||
|
||||
### :__tostring ()
|
||||
|
||||
|
|
@ -481,7 +481,7 @@ Returns a text representation of the LuaText, using Anselme's default formatting
|
|||
|
||||
Usage: `print(luatext)`
|
||||
|
||||
_defined at line 41 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `__tostring = function(self)`
|
||||
_defined at line 39 of [anselme/ast/Text.lua](../anselme/ast/Text.lua):_ `__tostring = function(self)`
|
||||
|
||||
---
|
||||
_file generated at 2024-11-17T15:00:50Z_
|
||||
_file generated at 2024-11-11T13:33:43Z_
|
||||
|
|
@ -2,11 +2,10 @@
|
|||
-- Behold! A documentation generator that doesn't try to be smart!
|
||||
-- Call this from the root anselme repository directory: `lua doc/gendocs.lua`
|
||||
|
||||
local utf8 = utf8 or (love and require("utf8") or require("lua-utf8"))
|
||||
local utf8 = utf8 or require("lua-utf8")
|
||||
|
||||
local files = {
|
||||
"doc/api.md",
|
||||
"doc/server.md",
|
||||
"doc/standard_library.md"
|
||||
}
|
||||
local source_link_prefix = "../"
|
||||
|
|
|
|||
336
doc/server.md
336
doc/server.md
|
|
@ -1,336 +0,0 @@
|
|||
Instead of the scripts running in the same Lua process as the one of your game, Anselme can run in a Client-Server mode. This allows:
|
||||
|
||||
* Anselme to run in a separate thread and therefore not affect your game's frame times (Anselme is not very fast)
|
||||
* to use Anselme other game engine that don't use Lua
|
||||
|
||||
The _server_ is the process that holds and process the Anselme state. Typically, the server would run in a separate process or thread that your game.
|
||||
|
||||
The _client_ connects to a server and sends instructions to execute Anselmes scripts and receive the response. Typically, the client correspond to your game.
|
||||
|
||||
For now, the whole system assumes that there is a single client per server - so you should not share a single server among serveral client.
|
||||
|
||||
How the Client and Server communicate between each other is defined using a RPC object.
|
||||
Out-of-the-box, Anselme provides RPC objects that can communicate over [LÖVE](https://www.love2d.org/) threads, and over [JSON-RPC 2.0](https://www.jsonrpc.org/specification); these can be easily created using the functions in [anselme.server](#anselme_server).
|
||||
If you want to implement a custom RPC mechanism, you can look at the existing implementations in `anselme/server/rpc/`.
|
||||
|
||||
Example usage in a LÖVE game:
|
||||
```lua
|
||||
local server = require("anselme.server")
|
||||
|
||||
-- create a new client+server
|
||||
local client = server.new_love_thread()
|
||||
client:load_stdlib()
|
||||
|
||||
-- load an anselme script file in a new branch
|
||||
local run_state = client:branch("block")
|
||||
run_state:run_file("script.ans")
|
||||
|
||||
-- start script
|
||||
run_state:step(handle_event)
|
||||
|
||||
-- callback to handle an anselme event
|
||||
function handle_event(event, data)
|
||||
if event == "text" then
|
||||
show_dialog_box {
|
||||
lines = data,
|
||||
on_dialog_box_closed = function()
|
||||
run_state:step(handle_event) -- resume script
|
||||
end
|
||||
}
|
||||
elseif event == "choice" then
|
||||
show_choice_dialog_box {
|
||||
choices = data,
|
||||
on_dialog_box_closed = function(choice_number)
|
||||
run_state:choose(choice_number)
|
||||
run_state:step(handle_event)
|
||||
end
|
||||
}
|
||||
elseif event == "return" then
|
||||
run_state:merge()
|
||||
run_state:remove() -- remove branch from server
|
||||
elseif event == "error" then
|
||||
print("error in anselme thread!", data)
|
||||
run_state:remove()
|
||||
end
|
||||
end
|
||||
|
||||
function love.update()
|
||||
client:process() -- handle messages coming from the server
|
||||
end
|
||||
```
|
||||
|
||||
# anselme.server
|
||||
|
||||
Main functions to create clients and servers.
|
||||
|
||||
### .new_love_thread ()
|
||||
|
||||
Starts a Server in a new LÖVE thread and returns a Client connected to that server.
|
||||
|
||||
Should be called from a [LÖVE](https://www.love2d.org/) game code only.
|
||||
|
||||
_defined at line 10 of [anselme/server/init.lua](../anselme/server/init.lua):_ `new_love_thread = function()`
|
||||
|
||||
### .new_json_rpc_server (send, receive)
|
||||
|
||||
Returns a new Server that communicate with a Client using JSON-RPC 2.0.
|
||||
|
||||
This does not define _how_ the two comminicate (through sockets, http, etc.), you will need to define this using the `send` and `receive` arguments.
|
||||
|
||||
`send(message)` is a function that send a single message to the associated Client.
|
||||
|
||||
`receive(block)` is a function that receive a single message from the associated Client (or `nil` if no message available). If `block` is true, the function is allowed to block execution until a message is received.
|
||||
|
||||
_defined at line 41 of [anselme/server/init.lua](../anselme/server/init.lua):_ `new_json_rpc_server = function(send, receive)`
|
||||
|
||||
### .new_json_rpc_client (send, receive)
|
||||
|
||||
Returns a new Client that communicate with a Server using JSON-RPC 2.0.
|
||||
|
||||
This does not define _how_ the two comminicate (through sockets, http, etc.), you will need to define this using the `send` and `receive` arguments.
|
||||
|
||||
`send(message)` is a function that send a single message to the associated Server.
|
||||
|
||||
`receive(block)` is a function that receive a single message from the associated Server (or `nil` if no message available). If `block` is true, the function is allowed to block execution until a message is received.
|
||||
|
||||
_defined at line 53 of [anselme/server/init.lua](../anselme/server/init.lua):_ `new_json_rpc_client = function(send, receive)`
|
||||
|
||||
|
||||
# Client
|
||||
|
||||
This is a Lua implementation of an Anselme client, with a nice API that mirrors the Anselme [State API](api.md#state) to communicate with the server.
|
||||
|
||||
Usage: create a Client object using the functions in the [anselme.server module](#anselme_server) and call `server:process()` regularly to process messages from the Server.
|
||||
|
||||
The API available here tries to follow the [State API](api.md#state) as much as possible, with the following differences:
|
||||
* functions that return a value in State take an additionnal argument `callback`:
|
||||
* if it is a function `callback(ret1, ret2, ...)`, it is called as soon as the return values `ret1, ret2, ...` are received. The function also returns the identifier `call_id` associated with the callback (to optionally cancel the callback later using `client:cancel(call_id)`).
|
||||
* if it is `nil`, return values are discarded;
|
||||
* if it is the string `"block"`, the call will block until the return values are received. The function returns these values directly.
|
||||
* functions that returns a `State` in State now returns a `Client`;
|
||||
* return values are converted to a simpler representation if possible (no metamethods, userdata or cycles) to make serialization simpler - in particular, Anselme values are automatically converted to Lua primitives.
|
||||
* a few new methods are introduced, see below.
|
||||
|
||||
Implementing a Client in other languages should be relatively easy: if your client language has a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) library, point it to the Anselme server you started using [`server.new_json_rpc_server()`](#new_json_rpc_server) and you're done.
|
||||
You should then be able to call any of the methods described in the [Server](#server).
|
||||
Additionnaly, if you plan to use the `define_rpc` or `define_local_rpc` server methods, you will need to implement the following remote method in your client that will be called by the server:
|
||||
* `call(function_id, ...)` where `function_id` (string) is the function identifier that was given when `define_rpc` or `define_local_rpc` was called, and `...` is a list of arguments. This must call the function associated with the `function_id` using the given arguments, and returns the values returned by the call (as a list of return values: `{ret1, ret2, ...}`).
|
||||
|
||||
### :process (block)
|
||||
|
||||
Process received messages.
|
||||
|
||||
Must be called regularly.
|
||||
If `block` is true, the function is allowed to block execution until a message is received.
|
||||
|
||||
_defined at line 51 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `process = function(self, block)`
|
||||
|
||||
### :cancel (call_id)
|
||||
|
||||
Cancel the callback associated with the call `call_id`.
|
||||
This does not stop the remote method execution; only prevent the callback from being called.
|
||||
|
||||
_defined at line 57 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `cancel = function(self, call_id)`
|
||||
|
||||
### :choose (i)
|
||||
|
||||
If the last event was a `choice`, choose the `i`-th choice.
|
||||
This must be called before calling `:step` again after receiving a choice event.
|
||||
|
||||
_defined at line 63 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `choose = function(self, i)`
|
||||
|
||||
### :remove ()
|
||||
|
||||
Remove the branch from the server.
|
||||
The branch (and therefore this Client branch) can't be used after calling this method.
|
||||
|
||||
_defined at line 68 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `remove = function(self)`
|
||||
|
||||
### :define_rpc (name, args, func)
|
||||
|
||||
Defines a function in the global scope, that calls the Lua function `func` on the Client when called.
|
||||
|
||||
The function will not be sent to the server; it will be directly executed on the client (i.e. your game code)
|
||||
each time a script on the server needs it to be called.
|
||||
|
||||
Usage: `client:define_rpc("teleport", "(position)", function(position) player:teleport(position) end)`
|
||||
|
||||
_defined at line 78 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `define_rpc = function(self, name, args, func)`
|
||||
|
||||
### :define_local_rpc (name, args, func)
|
||||
|
||||
Same as `:define_rpc`, but define the function in the current scope.
|
||||
|
||||
_defined at line 85 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `define_local_rpc = function(self, name, args, func)`
|
||||
|
||||
## Methods and fields that mirror the State API
|
||||
|
||||
### :load_stdlib (language)
|
||||
|
||||
Same as [`state:load_stdlib(language)`](api.md#load_stdlib-language).
|
||||
|
||||
_defined at line 95 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `load_stdlib = function(self, language)`
|
||||
|
||||
### .branch_id
|
||||
|
||||
Same as [`state.branch_id`](api.md#branch_id).
|
||||
|
||||
_defined at line 100 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `branch_id = "main",`
|
||||
|
||||
### .source_branch
|
||||
|
||||
Same as [`state.source_branch`](api.md#source_branch), but refers to the source `Client` instead of a `State`.
|
||||
|
||||
_defined at line 102 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `source_branch = nil,`
|
||||
|
||||
### :branch (branch_id, callback)
|
||||
|
||||
Same as [`state:branch(branch_id)`](api.md#branch-branch_id), but returns a new `Client` instead of a `State`.
|
||||
|
||||
_defined at line 104 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `branch = function(self, branch_id, callback)`
|
||||
|
||||
### :merge ()
|
||||
|
||||
Same as [`state:merge()`](api.md#merge).
|
||||
|
||||
_defined at line 113 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `merge = function(self)`
|
||||
|
||||
### :define (name, value, func_code, raw_mode)
|
||||
|
||||
Same as [`state:define(name, value, func, raw_mode)`](api.md#api.md#define-name-value-func-raw_mode), but if `func_code` is given, it must be a string containing the function code.
|
||||
|
||||
Note that the given code will be executed on the server, and that there is no sandboxing of any kind;
|
||||
|
||||
Example: `client:define("main", "print", "(message::is string)", "function(message) print(message) end")`.
|
||||
|
||||
_defined at line 122 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `define = function(self, name, value, func_code, raw_mode)`
|
||||
|
||||
### :define_local (name, value, func_code, raw_mode)
|
||||
|
||||
Same as [`define`](#define-name-value-func_code-raw_mode), but calls [`state:define_local(name, value, func, raw_mode)`](api.md#api.md#define_local-name-value-func-raw_mode).
|
||||
|
||||
_defined at line 126 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `define_local = function(self, name, value, func_code, raw_mode)`
|
||||
|
||||
### :defined (name, callback)
|
||||
|
||||
Same as [`state:defined(name)`](api.md#defined-name).
|
||||
|
||||
_defined at line 130 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `defined = function(self, name, callback)`
|
||||
|
||||
### :defined_local (name, callback)
|
||||
|
||||
Same as [`state:defined_local(name)`](api.md#defined_local-name).
|
||||
|
||||
_defined at line 134 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `defined_local = function(self, name, callback)`
|
||||
|
||||
### :save (callback)
|
||||
|
||||
Same as [`state:save()`](api.md#save).
|
||||
|
||||
_defined at line 139 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `save = function(self, callback)`
|
||||
|
||||
### :load (save)
|
||||
|
||||
Same as [`state:load(save)`](api.md#load-save).
|
||||
|
||||
_defined at line 143 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `load = function(self, save)`
|
||||
|
||||
### :active (callback)
|
||||
|
||||
Same as [`state:active()`](api.md#active).
|
||||
|
||||
_defined at line 148 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `active = function(self, callback)`
|
||||
|
||||
### :state (callback)
|
||||
|
||||
Same as [`state:state()`](api.md#state).
|
||||
|
||||
_defined at line 152 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `state = function(self, callback)`
|
||||
|
||||
### :run (code, source, tags)
|
||||
|
||||
Same as [`state:run(code, source, tags)`](api.md#run-code-source-tags).
|
||||
|
||||
_defined at line 156 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `run = function(self, code, source, tags)`
|
||||
|
||||
### :run_file (path, tags)
|
||||
|
||||
Same as [`state:run_file(code, source, tags)`](api.md#run_file-code-source-tags).
|
||||
|
||||
_defined at line 160 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `run_file = function(self, path, tags)`
|
||||
|
||||
### :step (callback)
|
||||
|
||||
Same as [`state:step)`](api.md#step), but returns:
|
||||
* for `text` and `choice` events, a list of lines `{ { { text = "line 1 part 2", tags = { ... } }, ... }, ... }` (in other word, [`TextEventData`](api.md#texteventdata) and [`ChoiceEventData`](api.md#choiceeventdata) stripped of everything but their list of text parts);
|
||||
* for `return` events, the return value converted to Lua primitives;
|
||||
* for other events, it will try to return the event data as-is.
|
||||
|
||||
_defined at line 167 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `step = function(self, callback)`
|
||||
|
||||
### :interrupt (code, source, tags)
|
||||
|
||||
Same as [`state:interrupt(code, source, tags)`](api.md#interrupt-code-source-tags).
|
||||
|
||||
_defined at line 171 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `interrupt = function(self, code, source, tags)`
|
||||
|
||||
### :eval (code, source, tags, callback)
|
||||
|
||||
Same as [`state:eval(code, source, tags)`](api.md#eval-code-source-tags), but the returned value is converted to Lua primitives.
|
||||
|
||||
_defined at line 175 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `eval = function(self, code, source, tags, callback)`
|
||||
|
||||
### :eval_local (code, source, tags, callback)
|
||||
|
||||
Same as [`state:eval_local(code, source, tags)`](api.md#eval_local-code-source-tags), but the returned value is converted to Lua primitives.
|
||||
|
||||
_defined at line 179 of [anselme/server/Client.lua](../anselme/server/Client.lua):_ `eval_local = function(self, code, source, tags, callback)`
|
||||
|
||||
|
||||
# Server
|
||||
|
||||
An Anselme server instance.
|
||||
|
||||
Usage: create a Server object using the functions in the [anselme.server module](#anselme_server) and call `server:process()` regularly to process messages from the Client.
|
||||
|
||||
If you are implementing your own client, the following methods are available to be remotely called by your client:
|
||||
* Note:
|
||||
* in all the following methods, the first parameter `branch_id` (string) is the id of the Anselme branch to operate on;
|
||||
* methods that return something always returns a list of return values: `{ ret1, ret2, ... }`.
|
||||
* `choose(branch_id, i)`: if the last event was a `choice`, choose the `i`-th (number) line in the choice list;
|
||||
* `remove(branch_id)`: removes the branch from the server; no further operation will be possible on the branch;
|
||||
* `load_stdlib(branch_id, language)`: calls [`state:load_stdlib(language)`](api.md#load_stdlib-language) on the branch;
|
||||
* `branch(branch_id[, new_branch_id])`: calls [`state:branch(branch_id)`](api.md#branch-branch_id) on the branch; returns the id of the new branch (string);
|
||||
* `merge(branch_id)`: calls [`state:merge()`](api.md#merge) on the branch;
|
||||
* `define(branch_id, name, args, func_code, raw_mode)`: calls [`state:define(branch_id, name, args, func, raw_mode)`](api.md#define-name-value-func-raw_mode) on the branch; if `func_code` is given, `func` will be a function generated from the Lua code `func_code` (string, example: `define("main", "print", "(message::is string)", "function(message) print(message) end")`). Note that whatever is in `func_code` will be executed on the server, and that there is no sandboxing of any kind;
|
||||
* `define_rpc(branch_id, name, args, func_id)`: defines a function in the branch that, when called, will call the remote method `call(func_id, ...)` on the client and block until it returns. In other words, this allows the Anselme script running on the server to transparently call the function that is associated with the id `func_id` on the client.
|
||||
* `define_local(branch_id, name, args, func_code, raw_mode)`: same as `define`, but calls [`state:define_local(branch_id, name, args, func, raw_mode)`](api.md#define_local-name-value-func-raw_mode);
|
||||
* `define_local_rpc(branch_id, name, args, func_id)`: same as `define_rpc`, but defines the function in the current scope;
|
||||
* `defined(branch_id, name)`: calls [`state:defined(name)`](api.md#defined-name) on the branch and returns its result;
|
||||
* `defined_local(branch_id, name)`: calls [`state:defined_local(name)`](api.md#defined_local-name) on the branch and returns its result;
|
||||
* `save(branch_id)`: calls [`state:save()`](api.md#save) on the branch and returns its result;
|
||||
* `load(branch_id, save)`: calls [`state:load(save)`](api.md#load-save) on the branch;
|
||||
* `active(branch_id)`: calls [`state:active()`](api.md#active) on the branch and returns its result;
|
||||
* `state(branch_id)`: calls [`state:state()`](api.md#state) on the branch and returns its result;
|
||||
* `run(branch_id, code, source, tags)`: calls [`state:run(code, source, tags)`](api.md#run-code-source-tags) on the branch;
|
||||
* `run_file(branch_id, path, tags)`: calls [`state:run_file(path, tags)`](api.md#run_file-path-tags) on the branch;
|
||||
* `step(branch_id)`: calls [`state:step()`](api.md#step) on the branch and returns:
|
||||
* for `text` and `choices` events, a list of lines `{ { { text = "line 1 part 2", tags = { ... } }, ... }, ... }` (in other word, [`TextEventData`](api.md#texteventdata) and [`ChoiceEventData`](api.md#choiceeventdata) stripped of everything but their list of text parts);
|
||||
* for `return` events, the return value converted to Lua;
|
||||
* for other events, it will try to return the event data as-is.
|
||||
* `interrupt(branch_id, code, source, tags)`: calls [`state:interrupt(code, source, tags)`](api.md#interrupt-code-source-tags) on the branch;
|
||||
* `eval(branch_id, code, source, tags)`: calls [`state:eval(code, source, tags)`](api.md#eval-code-source-tags) on the branch and returns its result, converted to Lua;
|
||||
* `eval_local(branch_id, code, source, tags)`: calls [`state:eval_local(code, source, tags)`](api.md#eval_local-code-source-tags) on the branch and returns its result, converted to Lua.
|
||||
|
||||
### :process (block)
|
||||
|
||||
Process received messages.
|
||||
|
||||
Must be called regularly.
|
||||
If `block` is true, the function is allowed to block execution until a message is received.
|
||||
|
||||
_defined at line 160 of [anselme/server/Server.lua](../anselme/server/Server.lua):_ `process = function(self, block)`
|
||||
|
||||
|
||||
---
|
||||
_file generated at 2024-11-17T15:00:50Z_
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
Instead of the scripts running in the same Lua process as the one of your game, Anselme can run in a Client-Server mode. This allows:
|
||||
|
||||
* Anselme to run in a separate thread and therefore not affect your game's frame times (Anselme is not very fast)
|
||||
* to use Anselme other game engine that don't use Lua
|
||||
|
||||
The _server_ is the process that holds and process the Anselme state. Typically, the server would run in a separate process or thread that your game.
|
||||
|
||||
The _client_ connects to a server and sends instructions to execute Anselmes scripts and receive the response. Typically, the client correspond to your game.
|
||||
|
||||
For now, the whole system assumes that there is a single client per server - so you should not share a single server among serveral client.
|
||||
|
||||
How the Client and Server communicate between each other is defined using a RPC object.
|
||||
Out-of-the-box, Anselme provides RPC objects that can communicate over [LÖVE](https://www.love2d.org/) threads, and over [JSON-RPC 2.0](https://www.jsonrpc.org/specification); these can be easily created using the functions in [anselme.server](#anselme_server).
|
||||
If you want to implement a custom RPC mechanism, you can look at the existing implementations in `anselme/server/rpc/`.
|
||||
|
||||
Example usage in a LÖVE game:
|
||||
```lua
|
||||
local server = require("anselme.server")
|
||||
|
||||
-- create a new client+server
|
||||
local client = server.new_love_thread()
|
||||
client:load_stdlib()
|
||||
|
||||
-- load an anselme script file in a new branch
|
||||
local run_state = client:branch("block")
|
||||
run_state:run_file("script.ans")
|
||||
|
||||
-- start script
|
||||
run_state:step(handle_event)
|
||||
|
||||
-- callback to handle an anselme event
|
||||
function handle_event(event, data)
|
||||
if event == "text" then
|
||||
show_dialog_box {
|
||||
lines = data,
|
||||
on_dialog_box_closed = function()
|
||||
run_state:step(handle_event) -- resume script
|
||||
end
|
||||
}
|
||||
elseif event == "choice" then
|
||||
show_choice_dialog_box {
|
||||
choices = data,
|
||||
on_dialog_box_closed = function(choice_number)
|
||||
run_state:choose(choice_number)
|
||||
run_state:step(handle_event)
|
||||
end
|
||||
}
|
||||
elseif event == "return" then
|
||||
run_state:merge()
|
||||
run_state:remove() -- remove branch from server
|
||||
elseif event == "error" then
|
||||
print("error in anselme thread!", data)
|
||||
run_state:remove()
|
||||
end
|
||||
end
|
||||
|
||||
function love.update()
|
||||
client:process() -- handle messages coming from the server
|
||||
end
|
||||
```
|
||||
|
||||
# anselme.server
|
||||
|
||||
{{anselme/server/init.lua}}
|
||||
|
||||
# Client
|
||||
|
||||
{{anselme/server/Client.lua}}
|
||||
|
||||
# Server
|
||||
|
||||
{{anselme/server/Server.lua}}
|
||||
|
|
@ -650,43 +650,19 @@ _defined at line 21 of [anselme/stdlib/string.lua](../anselme/stdlib/string.lua)
|
|||
|
||||
Concatenate two texts, returning a new text value.
|
||||
|
||||
_defined at line 19 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"_+_", "(a::is text, b::is text)",`
|
||||
_defined at line 16 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"_+_", "(a::is text, b::is text)",`
|
||||
|
||||
### txt::is text !
|
||||
|
||||
Write a text event in the event buffer using this text.
|
||||
|
||||
_defined at line 33 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"_!", "(txt::is text)",`
|
||||
_defined at line 30 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"_!", "(txt::is text)",`
|
||||
|
||||
### tag (txt::is text, tags::is struct)
|
||||
|
||||
Create and return a new text from `text`, with the tags from `tags` added.
|
||||
|
||||
_defined at line 41 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"tag", "(txt::is text, tags::is struct)",`
|
||||
|
||||
### group text by tag (tag::is string)
|
||||
|
||||
Cause future text events to be each split into separate text event every time the value of the tag with the key `tag_key` changes.
|
||||
|
||||
For example, with the following Anselme script:
|
||||
```
|
||||
group text by tag("speaker")
|
||||
speaker: "John" #
|
||||
| A
|
||||
| B
|
||||
speaker: "Lana" #
|
||||
| C
|
||||
speaker: "John" #
|
||||
| D
|
||||
```
|
||||
will produce three separate text events instead of one:
|
||||
* 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"`
|
||||
|
||||
This setting affect will affect the whole state.
|
||||
|
||||
_defined at line 67 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"group text by tag", "(tag::is string)",`
|
||||
_defined at line 38 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"tag", "(txt::is text, tags::is struct)",`
|
||||
|
||||
### write choice (text::is text, fn=attached block(keep return=true, default=($()())))
|
||||
|
||||
|
|
@ -702,13 +678,13 @@ write choice(| Choice |, $42)
|
|||
|
||||
If we are currently resuming to an anchor contained in `fn`, `fn` is directly called and the current choice event buffer will be discarded on flush, simulating the choice event buffer being sent to the host game and this choice being selected.
|
||||
|
||||
_defined at line 91 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"write choice", "(text::is text, fn=attached block(keep return=true, default=($()())))",`
|
||||
_defined at line 57 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"write choice", "(text::is text, fn=attached block(keep return=true, default=($()())))",`
|
||||
|
||||
### original -> translated
|
||||
|
||||
Add a translation so `original` is replaced with `translated`.
|
||||
|
||||
_defined at line 108 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"_->_", "(original::is(\"quote\"), translated::is(\"quote\"))",`
|
||||
_defined at line 74 of [anselme/stdlib/text.lua](../anselme/stdlib/text.lua):_ `"_->_", "(original::is(\"quote\"), translated::is(\"quote\"))",`
|
||||
|
||||
|
||||
# Symbols
|
||||
|
|
@ -1159,31 +1135,23 @@ If `search parent` is true, this will also search in parent scopes of the enviro
|
|||
|
||||
_defined at line 14 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"defined", "(env::is environment, var::is string, search parent::is boolean=false)",`
|
||||
|
||||
### defined (var::is string, search parent::is boolean=true)
|
||||
|
||||
Returns true if the variable named `var` is defined in in the current scope, false otherwise.
|
||||
|
||||
If `search parent` is true, this will also search in parent scopes of the current scope.
|
||||
|
||||
_defined at line 27 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"defined", "(var::is string, search parent::is boolean=true)",`
|
||||
|
||||
### c::is environment . s::is string
|
||||
|
||||
Gets the variable named `s` defined in the environment `c`.
|
||||
|
||||
_defined at line 39 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"_._", "(c::is environment, s::is string)",`
|
||||
_defined at line 26 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"_._", "(c::is environment, s::is string)",`
|
||||
|
||||
### c::is environment . s::is string = v
|
||||
|
||||
Sets the variable named `s` defined in the environment `c` to `v`.
|
||||
|
||||
_defined at line 48 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"_._", "(c::is environment, s::is string) = v",`
|
||||
_defined at line 35 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"_._", "(c::is environment, s::is string) = v",`
|
||||
|
||||
### c::is environment . s::is symbol = v
|
||||
|
||||
Define a new variable `s` in the environment `c` with the value `v`.
|
||||
|
||||
_defined at line 58 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"_._", "(c::is environment, s::is symbol) = v",`
|
||||
_defined at line 45 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"_._", "(c::is environment, s::is symbol) = v",`
|
||||
|
||||
### import (env::is environment, symbol tuple::is tuple)
|
||||
|
||||
|
|
@ -1196,7 +1164,7 @@ import(env, [:a, :b])
|
|||
:b = env.b
|
||||
```
|
||||
|
||||
_defined at line 76 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"import", "(env::is environment, symbol tuple::is tuple)",`
|
||||
_defined at line 63 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"import", "(env::is environment, symbol tuple::is tuple)",`
|
||||
|
||||
### import (env::is environment, symbol::is symbol)
|
||||
|
||||
|
|
@ -1208,14 +1176,14 @@ import(env, :a)
|
|||
:a = env.a
|
||||
```
|
||||
|
||||
_defined at line 92 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"import", "(env::is environment, symbol::is symbol)",`
|
||||
_defined at line 79 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"import", "(env::is environment, symbol::is symbol)",`
|
||||
|
||||
### load (path::is string)
|
||||
|
||||
Load an Anselme script from a file and run it.
|
||||
Returns the environment containing the exported variables from the file.
|
||||
|
||||
_defined at line 102 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"load", "(path::is string)",`
|
||||
_defined at line 89 of [anselme/stdlib/environment.lua](../anselme/stdlib/environment.lua):_ `"load", "(path::is string)",`
|
||||
|
||||
|
||||
# Typed values
|
||||
|
|
@ -1368,4 +1336,4 @@ _defined at line 14 of [anselme/stdlib/wrap.lua](../anselme/stdlib/wrap.lua):_ `
|
|||
|
||||
|
||||
---
|
||||
_file generated at 2024-11-17T15:00:50Z_
|
||||
_file generated at 2024-11-09T16:02:36Z_
|
||||
14
ideas.md
14
ideas.md
|
|
@ -4,16 +4,22 @@ Loosely ordered by willingness to implement.
|
|||
|
||||
---
|
||||
|
||||
Redundant `TextEventData:group_by` and `stdlib.group text by tag`: there can be only one.
|
||||
|
||||
---
|
||||
|
||||
Translation.
|
||||
|
||||
Do some more fancy scope work to allow the translation to access variables defined in the translation file?
|
||||
|
||||
---
|
||||
|
||||
Server API.
|
||||
|
||||
To be able to use Anselme in another language, it would be nice to be able to access it over some form of IPC.
|
||||
|
||||
No need to bother with networking I think. Just do some stdin/stdout handling, maybe use something like JSON-RPC: https://www.jsonrpc.org/specification (reminder: will need to add some metadata to specify content length, not aware of any streaming json lib in pure Lua - here's a rxi seal of quality library btw: https://github.com/rxi/json.lua). Or just make our own protocol around JSON.
|
||||
Issue: how to represent Anselme values? they will probably contain cycles, needs to access their methods, etc.
|
||||
Probably wise to look into how other do it. LSP: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
|
||||
|
||||
---
|
||||
|
||||
Return system.
|
||||
|
||||
Could be reused for exception handling or other purposes if accessible by the user.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ The overengineered game dialog scripting system in pure Lua.
|
|||
|
||||
This is version 2, a full rewrite. Version 1 is still available in the v1 branch.
|
||||
|
||||
Supported: Lua 5.4, Lua 5.3, LÖVE, LuaJIT (LuaJIT requires the utf8 module: `luarocks --lua-version=5.1 install luautf8`).
|
||||
Supported: Lua 5.4, Lua 5.3, LuaJIT (LuaJIT requires the utf8 module: `luarocks --lua-version=5.1 install luautf8`).
|
||||
Otherwise all needed files are included in the `anselme` directory.
|
||||
|
||||
Anselme is licensed under the ISC license, meaning you can basically use it for anything as long as you make the content of the [license file](license) appear somewhere. I would appreciate it if you don't use Anselme to commit war crimes though. If that's not enough for you or want better support, feel free to contact me, my integrity can be bought.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue