mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-28 00:59:31 +00:00
Rewrite
This commit is contained in:
parent
7a5a05ff34
commit
b233d7fa1e
138 changed files with 4369 additions and 1611 deletions
74
interpreter/common.lua
Normal file
74
interpreter/common.lua
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
local atypes, ltypes
|
||||
local eval
|
||||
|
||||
local common
|
||||
common = {
|
||||
-- flush interpreter state to global state
|
||||
flush_state = function(state)
|
||||
local global_vars = state.interpreter.global_state.variables
|
||||
for var, value in pairs(state.variables) do
|
||||
global_vars[var] = value
|
||||
end
|
||||
end,
|
||||
-- check truthyness of an anselme value
|
||||
truthy = function(val)
|
||||
if val.type == "number" then
|
||||
return val.value ~= 0
|
||||
elseif val.type == "nil" then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end,
|
||||
-- str: if success
|
||||
-- * nil, err: if error
|
||||
format = function(val)
|
||||
if atypes[val.type] and atypes[val.type].format then
|
||||
return atypes[val.type].format(val.value)
|
||||
else
|
||||
return nil, ("no formatter for type %q"):format(val.type)
|
||||
end
|
||||
end,
|
||||
-- lua value: if success
|
||||
-- * nil, err: if error
|
||||
to_lua = function(val)
|
||||
if atypes[val.type] and atypes[val.type].to_lua then
|
||||
return atypes[val.type].to_lua(val.value)
|
||||
else
|
||||
return nil, ("no Lua exporter for type %q"):format(val.type)
|
||||
end
|
||||
end,
|
||||
-- anselme value: if success
|
||||
-- * nil, err: if error
|
||||
from_lua = function(val)
|
||||
if ltypes[type(val)] and ltypes[type(val)].to_anselme then
|
||||
return ltypes[type(val)].to_anselme(val)
|
||||
else
|
||||
return nil, ("no Lua importer for type %q"):format(type(val))
|
||||
end
|
||||
end,
|
||||
-- string: if success
|
||||
-- * nil, err: if error
|
||||
eval_text = function(state, text)
|
||||
local s = ""
|
||||
for _, item in ipairs(text) do
|
||||
if type(item) == "string" then
|
||||
s = s .. item
|
||||
else
|
||||
local v, e = eval(state, item)
|
||||
if not v then return v, e end
|
||||
v, e = common.format(v)
|
||||
if not v then return v, e end
|
||||
s = s .. v
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = common
|
||||
local types = require((...):gsub("interpreter%.common$", "stdlib.types"))
|
||||
atypes, ltypes = types.anselme, types.lua
|
||||
eval = require((...):gsub("common$", "expression"))
|
||||
|
||||
return common
|
||||
184
interpreter/expression.lua
Normal file
184
interpreter/expression.lua
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
local expression
|
||||
local flush_state, to_lua, from_lua, eval_text
|
||||
|
||||
local run, run_block
|
||||
|
||||
--- evaluate an expression
|
||||
-- returns evaluated value if success
|
||||
-- returns nil, error if error
|
||||
local function eval(state, exp)
|
||||
-- number
|
||||
if exp.type == "number" then
|
||||
return {
|
||||
type = "number",
|
||||
value = exp.value
|
||||
}
|
||||
-- string
|
||||
elseif exp.type == "string" then
|
||||
local t, e = eval_text(state, exp.value)
|
||||
if not t then return t, e end
|
||||
return {
|
||||
type = "string",
|
||||
value = t
|
||||
}
|
||||
-- parentheses
|
||||
elseif exp.type == "parentheses" then
|
||||
return eval(state, exp.expression)
|
||||
-- list parentheses
|
||||
elseif exp.type == "list_parentheses" then
|
||||
if exp.expression then
|
||||
local v, e = eval(state, exp.expression)
|
||||
if not v then return v, e end
|
||||
if v.type == "list" then
|
||||
return v
|
||||
else
|
||||
return {
|
||||
type = "list",
|
||||
value = { v }
|
||||
}
|
||||
end
|
||||
else
|
||||
return {
|
||||
type = "list",
|
||||
value = {}
|
||||
}
|
||||
end
|
||||
-- variable
|
||||
elseif exp.type == "variable" then
|
||||
return state.variables[exp.name]
|
||||
-- list
|
||||
elseif exp.type == "list" then
|
||||
local l = {}
|
||||
local ast = exp
|
||||
while ast.type == "list" do
|
||||
local left, lefte = eval(state, ast.left)
|
||||
if not left then return left, lefte end
|
||||
table.insert(l, left)
|
||||
ast = ast.right
|
||||
end
|
||||
local right, righte = eval(state, ast)
|
||||
if not right then return right, righte end
|
||||
table.insert(l, right)
|
||||
return {
|
||||
type = "list",
|
||||
value = l
|
||||
}
|
||||
-- function
|
||||
elseif exp.type == "function" then
|
||||
local fn = exp.variant
|
||||
-- custom lua functions
|
||||
if fn.mode == "custom" then
|
||||
return fn.value(state, exp)
|
||||
else
|
||||
-- eval args: same as list, but only put vararg arguments in a separate list
|
||||
local l = {}
|
||||
if exp.argument then
|
||||
local vararg = fn.vararg or math.huge
|
||||
local i, ast = 1, exp.argument
|
||||
while ast.type == "list" and i < vararg do
|
||||
local left, lefte = eval(state, ast.left)
|
||||
if not left then return left, lefte end
|
||||
table.insert(l, left)
|
||||
ast = ast.right
|
||||
i = i + 1
|
||||
end
|
||||
local right, righte = eval(state, ast)
|
||||
if not right then return right, righte end
|
||||
table.insert(l, right)
|
||||
end
|
||||
if fn.vararg and #l < fn.vararg then -- empty list vararg
|
||||
table.insert(l, { type = "list", value = {} })
|
||||
end
|
||||
-- anselme function
|
||||
if type(fn.value) == "table" then
|
||||
-- paragraph & paragraph decorator
|
||||
if fn.value.type == "paragraph" or fn.value.paragraph then
|
||||
local r, e
|
||||
if fn.value.type == "paragraph" then
|
||||
r, e = run_block(state, fn.value.child, false)
|
||||
if e then return r, e end
|
||||
state.variables[fn.value.namespace.."👁️"] = {
|
||||
type = "number",
|
||||
value = state.variables[fn.value.namespace.."👁️"].value + 1
|
||||
}
|
||||
state.variables[fn.value.parent_function.namespace.."🏁"] = {
|
||||
type = "string",
|
||||
value = fn.value.name
|
||||
}
|
||||
flush_state(state)
|
||||
if r then
|
||||
return r, e
|
||||
elseif not exp.explicit_call then
|
||||
r, e = run(state, fn.value.parent_block, true, fn.value.parent_position+1)
|
||||
else
|
||||
r = { type = "nil", value = nil }
|
||||
end
|
||||
elseif exp.explicit_call then
|
||||
r, e = run(state, fn.value.parent_block, false, fn.value.parent_position, fn.value.parent_position)
|
||||
else
|
||||
r, e = run(state, fn.value.parent_block, true, fn.value.parent_position)
|
||||
end
|
||||
if not r then return r, e end
|
||||
return r
|
||||
-- function
|
||||
elseif fn.value.type == "function" then
|
||||
-- set args
|
||||
for j, param in ipairs(fn.value.params) do
|
||||
state.variables[param] = l[j]
|
||||
end
|
||||
-- eval function
|
||||
local r, e
|
||||
if exp.explicit_call or state.variables[fn.value.namespace.."🏁"].value == "" then
|
||||
r, e = run(state, fn.value.child)
|
||||
-- resume at last paragraph
|
||||
else
|
||||
local expr, err = expression(state.variables[fn.value.namespace.."🏁"].value, state, "")
|
||||
if not expr then return expr, err end
|
||||
r, e = eval(state, expr)
|
||||
end
|
||||
if not r then return r, e end
|
||||
state.variables[fn.value.namespace.."👁️"] = {
|
||||
type = "number",
|
||||
value = state.variables[fn.value.namespace.."👁️"].value + 1
|
||||
}
|
||||
flush_state(state)
|
||||
return r
|
||||
else
|
||||
return nil, ("unknown function type %q"):format(fn.value.type)
|
||||
end
|
||||
-- lua functions
|
||||
else
|
||||
if fn.mode == "raw" then
|
||||
return fn.value(unpack(l))
|
||||
else
|
||||
local l_lua = {}
|
||||
for _, v in ipairs(l) do
|
||||
table.insert(l_lua, to_lua(v))
|
||||
end
|
||||
local r, e
|
||||
if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall
|
||||
r, e = true, fn.value(unpack(l_lua))
|
||||
else
|
||||
r, e = pcall(fn.value, unpack(l_lua)) -- pcall to produce a more informative error message (instead of full coroutine crash)
|
||||
end
|
||||
if r then
|
||||
return from_lua(e)
|
||||
else
|
||||
return nil, ("%s; in Lua function %q"):format(e, exp.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil, ("unknown expression %q"):format(tostring(exp.type))
|
||||
end
|
||||
end
|
||||
|
||||
package.loaded[...] = eval
|
||||
run = require((...):gsub("expression$", "interpreter")).run
|
||||
run_block = require((...):gsub("expression$", "interpreter")).run_block
|
||||
expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
|
||||
local common = require((...):gsub("expression$", "common"))
|
||||
flush_state, to_lua, from_lua, eval_text = common.flush_state, common.to_lua, common.from_lua, common.eval_text
|
||||
|
||||
return eval
|
||||
196
interpreter/interpreter.lua
Normal file
196
interpreter/interpreter.lua
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
local eval
|
||||
local truthy, flush_state, to_lua, eval_text
|
||||
|
||||
local function write_event(state, type, data)
|
||||
if state.interpreter.event_buffer and state.interpreter.event_type ~= type then
|
||||
error("previous event has not been flushed")
|
||||
end
|
||||
if not state.interpreter.event_buffer then
|
||||
state.interpreter.event_type = type
|
||||
state.interpreter.event_buffer = {}
|
||||
end
|
||||
table.insert(state.interpreter.event_buffer, { data = data, tags = state.interpreter.tags[#state.interpreter.tags] or {} })
|
||||
end
|
||||
|
||||
local tags = {
|
||||
push = function(self, state, val)
|
||||
local new = {}
|
||||
-- copy
|
||||
local last = state.interpreter.tags[#state.interpreter.tags] or {}
|
||||
for k,v in pairs(last) do new[k] = v end
|
||||
-- merge with new values
|
||||
if val.type ~= "list" then val = { type = "list", value = { val } } end
|
||||
for k, v in pairs(to_lua(val)) do new[k] = v end
|
||||
-- add
|
||||
table.insert(state.interpreter.tags, new)
|
||||
end,
|
||||
pop = function(self, state)
|
||||
table.remove(state.interpreter.tags)
|
||||
end
|
||||
}
|
||||
|
||||
local run_block
|
||||
|
||||
-- returns var in case of success and there is a return
|
||||
-- return nil in case of success and there is no return
|
||||
-- return nil, err in case of error
|
||||
local function run_line(state, line)
|
||||
-- store line
|
||||
state.interpreter.running_line = line
|
||||
-- condition decorator
|
||||
local skipped = false
|
||||
if line.condition then
|
||||
local v, e = eval(state, line.condition)
|
||||
if not v then return v, ("%s; at line %s"):format(e, line.line) end
|
||||
skipped = not truthy(v)
|
||||
end
|
||||
if not skipped then
|
||||
-- tag decorator
|
||||
if line.tag then
|
||||
local v, e = eval(state, line.tag)
|
||||
if not v then return v, ("%s; in tag decorator at line %s"):format(e, line.line) end
|
||||
tags:push(state, v)
|
||||
end
|
||||
-- line types
|
||||
if line.type == "condition" then
|
||||
state.interpreter.last_condition_success = nil
|
||||
local v, e = eval(state, line.expression)
|
||||
if not v then return v, ("%s; at line %s"):format(e, line.line) end
|
||||
if truthy(v) then
|
||||
state.interpreter.last_condition_success = true
|
||||
v, e = run_block(state, line.child)
|
||||
if e then return v, e end
|
||||
if v then return v end
|
||||
end
|
||||
elseif line.type == "else-condition" then
|
||||
if not state.interpreter.last_condition_success then
|
||||
local v, e = eval(state, line.expression)
|
||||
if not v then return v, ("%s; at line %s"):format(e, line.line) end
|
||||
if truthy(v) then
|
||||
state.interpreter.last_condition_success = true
|
||||
v, e = run_block(state, line.child)
|
||||
if e then return v, e end
|
||||
if v then return v end
|
||||
end
|
||||
end
|
||||
elseif line.type == "choice" then
|
||||
local t, er = eval_text(state, line.text)
|
||||
if not t then return t, er end
|
||||
table.insert(state.interpreter.choice_available, line.child)
|
||||
write_event(state, "choice", t)
|
||||
elseif line.type == "tag" then
|
||||
if line.expression then
|
||||
local v, e = eval(state, line.expression)
|
||||
if not v then return v, ("%s; at line %s"):format(e, line.line) end
|
||||
tags:push(state, v)
|
||||
end
|
||||
local v, e = run_block(state, line.child)
|
||||
if line.expression then tags:pop(state) end
|
||||
if e then return v, e end
|
||||
if v then return v end
|
||||
elseif line.type == "return" then
|
||||
local v, e
|
||||
if line.expression then
|
||||
v, e = eval(state, line.expression)
|
||||
if not v then return v, ("%s; at line %s"):format(e, line.line) end
|
||||
end
|
||||
return v
|
||||
elseif line.type == "text" then
|
||||
local t, er = eval_text(state, line.text)
|
||||
if not t then return t, ("%s; at line %s"):format(er, line.line) end
|
||||
write_event(state, "text", t)
|
||||
elseif line.type == "flush_events" then
|
||||
while state.interpreter.event_buffer do
|
||||
local type, buffer = state.interpreter.event_type, state.interpreter.event_buffer
|
||||
state.interpreter.event_type = nil
|
||||
state.interpreter.event_buffer = nil
|
||||
-- yield
|
||||
coroutine.yield(type, buffer)
|
||||
-- run choice
|
||||
if type == "choice" then
|
||||
local sel = state.interpreter.choice_selected
|
||||
state.interpreter.choice_selected = nil
|
||||
if not sel or sel < 1 or sel > #state.interpreter.choice_available then
|
||||
return nil, "invalid choice"
|
||||
else
|
||||
local choice = state.interpreter.choice_available[sel]
|
||||
state.interpreter.choice_available = {}
|
||||
local v, e = run_block(state, choice)
|
||||
if e then return v, e end
|
||||
if v then return v end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif line.type ~= "paragraph" then
|
||||
return nil, ("unknown line type %q; line %s"):format(line.type, line.line)
|
||||
end
|
||||
-- tag decorator
|
||||
if line.tag then
|
||||
tags:pop(state)
|
||||
end
|
||||
-- paragraph decorator
|
||||
if line.paragraph then
|
||||
state.variables[line.namespace.."👁️"] = {
|
||||
type = "number",
|
||||
value = state.variables[line.namespace.."👁️"].value + 1
|
||||
}
|
||||
state.variables[line.parent_function.namespace.."🏁"] = {
|
||||
type = "string",
|
||||
value = line.name
|
||||
}
|
||||
flush_state(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- returns var in case of success and there is a return
|
||||
-- return nil in case of success and there is no return
|
||||
-- return nil, err in case of error
|
||||
run_block = function(state, block, run_whole_function, i, j)
|
||||
i = i or 1
|
||||
local len = math.min(#block, j or math.huge)
|
||||
while i <= len do
|
||||
local v, e = run_line(state, block[i])
|
||||
if e then return v, e end
|
||||
if v then return v end
|
||||
i = i + 1
|
||||
end
|
||||
-- go up hierarchy if asked to run the whole function
|
||||
if run_whole_function and block.parent_line and block.parent_line.type ~= "function" then
|
||||
local parent_line = block.parent_line
|
||||
local v, e = run_block(state, parent_line.parent_block, run_whole_function, parent_line.parent_position+1)
|
||||
if e then return v, e end
|
||||
if v then return v, e end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- returns var in case of success
|
||||
-- return nil, err in case of error
|
||||
local function run(state, block, run_whole_function, i, j)
|
||||
-- run
|
||||
local v, e = run_block(state, block, run_whole_function, i, j)
|
||||
if e then return v, e end
|
||||
if v then
|
||||
return v
|
||||
else
|
||||
-- default no return value
|
||||
return {
|
||||
type = "nil",
|
||||
value = nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local interpreter = {
|
||||
run = run,
|
||||
run_block = run_block,
|
||||
run_line = run_line
|
||||
}
|
||||
|
||||
package.loaded[...] = interpreter
|
||||
eval = require((...):gsub("interpreter$", "expression"))
|
||||
local common = require((...):gsub("interpreter$", "common"))
|
||||
truthy, flush_state, to_lua, eval_text = common.truthy, common.flush_state, common.to_lua, common.eval_text
|
||||
|
||||
return interpreter
|
||||
Loading…
Add table
Add a link
Reference in a new issue