mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Rewrite
This commit is contained in:
parent
7a5a05ff34
commit
b233d7fa1e
138 changed files with 4369 additions and 1611 deletions
193
README.md
193
README.md
|
|
@ -1,193 +0,0 @@
|
||||||
Anselme quick reference
|
|
||||||
=======================
|
|
||||||
|
|
||||||
Anselme will read script files line per line, starting from the start of the file.
|
|
||||||
|
|
||||||
Every line can have children: a new line prefixed with a tabulation, or more if it's a children of a children, and so on.
|
|
||||||
|
|
||||||
Anselme will automatically read the top-level lines. Children reading will be decided by their parents.
|
|
||||||
|
|
||||||
Lines types and their properties
|
|
||||||
--------------------------------
|
|
||||||
* Lines starting with a character which isn't listed below are text. They will be said out loud. Text formatting apply. If the line ends with a \, the text will not be immediately sent to the engine (it will be sent along with the next text line encountered, concatenated).
|
|
||||||
|
|
||||||
Example: `Hello world!`
|
|
||||||
|
|
||||||
No children.
|
|
||||||
|
|
||||||
No variables.
|
|
||||||
|
|
||||||
* Lines starting with ( are comments.
|
|
||||||
|
|
||||||
Example: `(Important comment)`
|
|
||||||
|
|
||||||
Their children are never read nor parsed, so it can be used for multiline comments.
|
|
||||||
|
|
||||||
No variables.
|
|
||||||
|
|
||||||
* Lines starting with § are paragraphs. A paragraph can have parameters, between parantheses and seperated by commas. Parantheses can be ommited if there are no parameters. Missing parent paragraphs will be created.
|
|
||||||
|
|
||||||
Example: `§ the start of the adventure (hero name, size of socks collection)`
|
|
||||||
|
|
||||||
Their children are only read after a redirection to this paragraph.
|
|
||||||
|
|
||||||
Variables:
|
|
||||||
* 👁️: number of times the paragraph definition line has been encoutered before
|
|
||||||
* 🗨️: number of times the paragraph's children have been executed before
|
|
||||||
|
|
||||||
* Lines starting with > are choices. The play can choose between this choice and every immediately following choice line. Text formatting apply. If a choice ends with a \, the choice will not immediately be sent to the engine (it will be send along with the next choice encoutered, with all choices available).
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
> Yes.
|
|
||||||
Neat.
|
|
||||||
> No.
|
|
||||||
I'm sad now.
|
|
||||||
```
|
|
||||||
|
|
||||||
Its children will be read if the player select this choice.
|
|
||||||
|
|
||||||
No variables.
|
|
||||||
|
|
||||||
* Lines starting with : are variable definition. They will define and set to a specific value a currently undefined variable, which is searched in the closest paragraph only. Missing paragraphs will be created. They will always be run at compile time.
|
|
||||||
|
|
||||||
Example: `:(variable*2) variableSquared`
|
|
||||||
|
|
||||||
No children.
|
|
||||||
|
|
||||||
No variables.
|
|
||||||
|
|
||||||
* Lines starting with =, +, -, *, /, %, ^, !, &, | are variable assignements. They will change the value of a variable, searched as described in Variables. When asked to change the value of a paragraph, special behaviour may occur; see Aliases.
|
|
||||||
|
|
||||||
Example: `+1 life point`
|
|
||||||
|
|
||||||
No children.
|
|
||||||
|
|
||||||
No variables.
|
|
||||||
|
|
||||||
* Lines starting with ~ are redirections. They usually instruct the game to go to a specific paragraph (see Paragraph selection) and resume reading, but they will in practive evaluate any expression given to them. If the expression returns a paragraph, it will automatically be called (unless you redefine the ? operator). Redirections that immediately follow this one will only be read if this redirection failed (like a elseif). Expression default to true if not specified.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
~ the start of the adventure ("John Pizzapone", 9821)
|
|
||||||
|
|
||||||
~ life point > 5
|
|
||||||
Life is good
|
|
||||||
~
|
|
||||||
NOT GOOD ENOUGH
|
|
||||||
```
|
|
||||||
|
|
||||||
Their children will be run only if the paragraph returns a truthy value.
|
|
||||||
|
|
||||||
No variables.
|
|
||||||
|
|
||||||
* Lines starting with @ are value return statements. They set the return value of the current paragraph.
|
|
||||||
|
|
||||||
Example: `@1+1`
|
|
||||||
|
|
||||||
No children.
|
|
||||||
|
|
||||||
No variables.
|
|
||||||
|
|
||||||
* Lines starting with # are tags marker. They will define tags for all text sent from their children. Name and value are expressions.
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
# "colour": "red", "big"
|
|
||||||
Hey.
|
|
||||||
```
|
|
||||||
|
|
||||||
"Hey" will be sent along with the tag table { colour = "red", "big" }.
|
|
||||||
|
|
||||||
Their children are always run.
|
|
||||||
|
|
||||||
No variables.
|
|
||||||
|
|
||||||
Line decorators
|
|
||||||
---------------
|
|
||||||
Every line can be suffixed with a `~` and a following condition; the line will only be run when the condition is verified.
|
|
||||||
|
|
||||||
Similarly, every line can be suffixed with a `#` and a list of tags that will be set for this line (won't affect its children). Tag decorators must be placed before condition decorators.
|
|
||||||
|
|
||||||
Lines can also be suffixed with a `§` and a name to behave like a paragraph (they will have variables, and can be redirected to).
|
|
||||||
|
|
||||||
Text formatting
|
|
||||||
---------------
|
|
||||||
Stuff inside braces `{ }` will be replaced with the associated expression content. If the expression returns a paragraph, it will automatically be called.
|
|
||||||
|
|
||||||
Tags
|
|
||||||
----
|
|
||||||
Tags can be specified using the `#` line or decorator. If the expression returns a list, all of its elements will be recursively extracted and the final list will be provided to the engine. Paragraphs in the list will be automatically evaluated. If pairs are present, they will be used as key-value pairs in the tags table.
|
|
||||||
|
|
||||||
Expressions
|
|
||||||
-----------
|
|
||||||
A formula. Available operators: `?` (thruth test), `&`, `|` (boolean and, or), `!` (boolean not), `+`, `-`, `*`, `/`, `//`, `%`, `^` (arithmetic), `>`, `<`, `>=`,`<=` (comparaison), `=`, `!=` (value (in)equality), `:` (pair), `,` (list).
|
|
||||||
|
|
||||||
Unusual operators:
|
|
||||||
* `?paragraph` will recursively evaluate the paragraph until a non-paragraph is found, and returns a boolean
|
|
||||||
* `-string` will reverse the string
|
|
||||||
* `string + string/number` will concatenate
|
|
||||||
* `string - number` will returns everything before/after the last/first number characters
|
|
||||||
* `string - string` will remove every string from the string
|
|
||||||
* `string * number` will repeat the string
|
|
||||||
* `string / number` will returns the last/first number characters
|
|
||||||
* `string/number % string/number` will returns the position of string in string if found, no if not found
|
|
||||||
* `string/number ^ boolean` will uppercase/lowercase the string
|
|
||||||
|
|
||||||
Paragraph can have custom binary operator behaviour by having a sub paragraph named like `_operator_` (eg, `_+_` for the + operator). The function will receive (left, right) as parameters. This does not apply to lazy operators (`&`, `|`), you can only change their behaviour by changing the behaviour of the truth test (var is true if and only if `?var = 1`), i.e., via redefining the `?` operator).
|
|
||||||
|
|
||||||
Similarly, unary operators can be redefined by using the name `-_`.
|
|
||||||
|
|
||||||
Assignement operators can be redefined using their name (eg, `=` for direct assignement or `+` for addition).
|
|
||||||
|
|
||||||
Parantheses can be used for priority management.
|
|
||||||
|
|
||||||
Anselme test the falsity of value by comparing it with `0`. Everything else is true, including the string `"0"`.
|
|
||||||
|
|
||||||
Variables can be used by writing their name. Straigthforward.
|
|
||||||
|
|
||||||
Variables
|
|
||||||
---------
|
|
||||||
Variables names can contain every character except `. { } § > < ( ) ~ + - * / % ^ = ! & | : ,` and space.
|
|
||||||
|
|
||||||
Value type:
|
|
||||||
* number: `0`, `1`, ...
|
|
||||||
* string: `"Text"`. Text formatting applies.
|
|
||||||
* pair: `name: value`
|
|
||||||
* list: `value1, value2, ...`
|
|
||||||
* paragraph: a reference to a paragraph
|
|
||||||
* luafunction: function defined by the engine
|
|
||||||
|
|
||||||
Variables need to be defined before use. Their type cannot be changed after definition.
|
|
||||||
|
|
||||||
The same rules as in Paragraph selection apply.
|
|
||||||
|
|
||||||
Functions
|
|
||||||
---------
|
|
||||||
Paragraphs can be used like functions. Use `(var1, var2)` to specify parameters in the paragraph definition. Theses variables will be set in the paragraph when it is called. Parantheses are not needed for functions without parameters.
|
|
||||||
|
|
||||||
When called in an expression, the paragraph will return a value that can be redefined using a `@` line. By default, the return value is the empty string.
|
|
||||||
|
|
||||||
Addresses
|
|
||||||
---------
|
|
||||||
The path to a paragraph, subparagraph or any variable is called an address.
|
|
||||||
|
|
||||||
Anselme will search for variables from the current indentation level up to the top-level.
|
|
||||||
|
|
||||||
You can select sub-variables using a space between the parent paragraph name and its children, and so on.
|
|
||||||
|
|
||||||
You can select sub-variables using expression by putting them between braces (will automatically evaluate paragraph). For example,
|
|
||||||
|
|
||||||
~ foo {"bar"}
|
|
||||||
|
|
||||||
will select foo bar.
|
|
||||||
|
|
||||||
When a sub-variables is not found directly, it will be searched in the parent's return values.
|
|
||||||
|
|
||||||
Engine defined functions
|
|
||||||
------------------------
|
|
||||||
Functions (same as paragraphs) can be defined by the game engine. These always will be searched first. See Anselme's public API on how to add them (at the end of this file).
|
|
||||||
|
|
||||||
Built-in functions:
|
|
||||||
* `↩️(destiation name, source name)` will set up an alias so when the name "source name" is used but not found, it will be replaced with "destination name"
|
|
||||||
|
|
||||||
Anselme's public interface is definied at the end of anselme.can.
|
|
||||||
11
TODO
11
TODO
|
|
@ -1,11 +0,0 @@
|
||||||
TODO: test/check redirections consistency/coverage
|
|
||||||
TODO: merge new scripts with an old state
|
|
||||||
TODO: translation thing. Linked with script merging. Simplest solution (which does not imply adding uuids to every text line in every file) would be to use a mapping file, which maps every save-relevant variable to its name in a translation.
|
|
||||||
|
|
||||||
(TODO changer anselme pour les sauvegardes - j'ai une feuille dessus, mais iirc la bonne solution c'était de changer les variables pour référerer au dernier checkpoint (paragraph / choix / if) et de commit les données qu'aux checkponts (autorise changements de texte, mais à voir comment identifier uniquement les choix et ifs...))
|
|
||||||
(TODO: autoriser type de variables custom (par ex list): définir type et actions avec les opérateurs)
|
|
||||||
(genre ici un type inventory: :inventory() raquettes / +"raquette sans fil" raquettes) (utiliser probablement les opérateurs custom)
|
|
||||||
(TODO: méthodes ? genre string:gsub(truc) signifie gsub(string, truc) idk ou juste des méthodes comme Lua (mais engine-defined))
|
|
||||||
|
|
||||||
TODO: functions with default value for arguments / named parameters. Use : as name-value delimiter (like with tags)
|
|
||||||
TODO: list methods
|
|
||||||
1361
anselme.can
1361
anselme.can
File diff suppressed because it is too large
Load diff
334
anselme.lua
Normal file
334
anselme.lua
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
-- anselme module
|
||||||
|
local anselme = {
|
||||||
|
-- version
|
||||||
|
version = "0.12.0",
|
||||||
|
--- currently running interpreter
|
||||||
|
running = nil
|
||||||
|
}
|
||||||
|
package.loaded[...] = anselme
|
||||||
|
|
||||||
|
-- load libs
|
||||||
|
local preparse = require((...):gsub("anselme$", "parser.preparser"))
|
||||||
|
local postparse = require((...):gsub("anselme$", "parser.postparser"))
|
||||||
|
local expression = require((...):gsub("anselme$", "parser.expression"))
|
||||||
|
local eval = require((...):gsub("anselme$", "interpreter.expression"))
|
||||||
|
local run_line = require((...):gsub("anselme$", "interpreter.interpreter")).run_line
|
||||||
|
local to_lua = require((...):gsub("anselme$", "interpreter.common")).to_lua
|
||||||
|
local stdfuncs = require((...):gsub("anselme$", "stdlib.functions"))
|
||||||
|
|
||||||
|
-- wrappers for love.filesystem / luafilesystem
|
||||||
|
local function list_directory(path)
|
||||||
|
local t = {}
|
||||||
|
if love then
|
||||||
|
t = love.filesystem.getDirectoryItems(path)
|
||||||
|
else
|
||||||
|
local lfs = require("lfs")
|
||||||
|
for item in lfs.dir(path) do
|
||||||
|
table.insert(t, path.."/"..item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
local function is_directory(path)
|
||||||
|
if love then
|
||||||
|
return not not love.filesystem.getInfo(path, "directory")
|
||||||
|
else
|
||||||
|
local lfs = require("lfs")
|
||||||
|
return lfs.attributes(path, "mode") == "directory"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- interpreter methods
|
||||||
|
local interpreter_methods = {
|
||||||
|
-- VM this interpreter belongs to
|
||||||
|
vm = nil,
|
||||||
|
|
||||||
|
--- run the VM until the next event
|
||||||
|
-- returns event, data; if event is "return" or "error", the interpreter must not be stepped further
|
||||||
|
step = function(self)
|
||||||
|
-- check status
|
||||||
|
if coroutine.status(self.state.interpreter.coroutine) ~= "suspended" then
|
||||||
|
return "error", ("can't step interpreter because it has already finished or is already running (coroutine status: %s)"):format(coroutine.status(self.state.interpreter.coroutine))
|
||||||
|
end
|
||||||
|
-- handle interrupt
|
||||||
|
if self.state.interpreter.interrupt then
|
||||||
|
local expr = self.state.interpreter.interrupt
|
||||||
|
if expr == true then
|
||||||
|
return "return", "" -- nothing to do after interrupt
|
||||||
|
else
|
||||||
|
local line = self.state.interpreter.running_line
|
||||||
|
local namespace = self:current_namespace()
|
||||||
|
-- replace state with interrupted state
|
||||||
|
local exp, err = expression(expr, self.state.interpreter.global_state, namespace or "")
|
||||||
|
if not exp then return "error", ("%s; during interrupt %q at line %s"):format(err, expr, line and line.line or 0) end
|
||||||
|
local r, e = self.vm:run(exp)
|
||||||
|
if not r then return "error", e end
|
||||||
|
self.state = r.state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- run
|
||||||
|
local previous = anselme.running
|
||||||
|
anselme.running = self
|
||||||
|
local success, event, data = coroutine.resume(self.state.interpreter.coroutine)
|
||||||
|
anselme.running = previous
|
||||||
|
if not success then return "error", event end
|
||||||
|
return event, data
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- select an answer
|
||||||
|
-- returns self
|
||||||
|
choose = function(self, i)
|
||||||
|
self.state.interpreter.choice_selected = tonumber(i)
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- interrupt the vm on the next step, executing an expression is specified
|
||||||
|
-- returns self
|
||||||
|
interrupt = function(self, expr)
|
||||||
|
self.state.interpreter.interrupt = expr or true
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- search closest namespace from last run line
|
||||||
|
current_namespace = function(self)
|
||||||
|
local line = self.state.interpreter.running_line
|
||||||
|
local namespace = ""
|
||||||
|
if line then
|
||||||
|
local cur_line = line
|
||||||
|
namespace = cur_line.namespace
|
||||||
|
while not namespace do
|
||||||
|
local block = cur_line.parent_block
|
||||||
|
if not block.parent_line then break end -- reached root
|
||||||
|
cur_line = block.parent_line
|
||||||
|
namespace = cur_line.namespace
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return namespace
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- run an expression: may trigger events and must be called from within the interpreter coroutine
|
||||||
|
-- return lua value
|
||||||
|
run = function(self, expr, namespace)
|
||||||
|
-- parse
|
||||||
|
local err
|
||||||
|
if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state.interpreter.global_state, namespace or "") end
|
||||||
|
if not expr then coroutine.yield("error", err) end
|
||||||
|
-- run
|
||||||
|
local r, e = eval(self.state, expr)
|
||||||
|
if not r then coroutine.yield("error", e) end
|
||||||
|
if self.state.interpreter.event_buffer then -- flush final events
|
||||||
|
local rf, re = run_line(self.state, { type = "flush_events" })
|
||||||
|
if re then coroutine.yield("error", re) end
|
||||||
|
if rf then r = rf end
|
||||||
|
end
|
||||||
|
return to_lua(r)
|
||||||
|
end,
|
||||||
|
--- evaluate an expression
|
||||||
|
-- return value in case of success
|
||||||
|
-- return nil, err in case of error
|
||||||
|
eval = function(self, expr, namespace)
|
||||||
|
-- parse
|
||||||
|
local err
|
||||||
|
if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state.interpreter.global_state, namespace or "") end
|
||||||
|
if not expr then return nil, err end
|
||||||
|
-- run
|
||||||
|
local co = coroutine.create(function()
|
||||||
|
local r, e = eval(self.state, expr)
|
||||||
|
if not r then return "error", e end
|
||||||
|
return "return", to_lua(r)
|
||||||
|
end)
|
||||||
|
local previous = anselme.running
|
||||||
|
anselme.running = self
|
||||||
|
local success, event, data = coroutine.resume(co)
|
||||||
|
anselme.running = previous
|
||||||
|
if not success then
|
||||||
|
return nil, event
|
||||||
|
elseif event ~= "return" then
|
||||||
|
return nil, ("evaluated expression generated an %q event"):format(event)
|
||||||
|
else
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
interpreter_methods.__index = interpreter_methods
|
||||||
|
|
||||||
|
--- vm methods
|
||||||
|
local vm_mt = {
|
||||||
|
--- load code
|
||||||
|
-- return self in case of success
|
||||||
|
-- returns nil, err in case of error
|
||||||
|
loadstring = function(self, str, name)
|
||||||
|
local s, e = preparse(self.state, str, name or "")
|
||||||
|
if not s then return s, e end
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
loadfile = function(self, path, name)
|
||||||
|
local f, e = io.open(path, "r")
|
||||||
|
if not f then return f, e end
|
||||||
|
local s, err = self:loadstring(f:read("*a"), name or "")
|
||||||
|
f:close()
|
||||||
|
if not s then return s, err end
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
loaddirectory = function(self, path, name)
|
||||||
|
if not name then name = "" end
|
||||||
|
name = name == "" and "" or name.."."
|
||||||
|
for _, item in ipairs(list_directory(path)) do
|
||||||
|
if item:match("[^%.]") then
|
||||||
|
local p = path.."/"..item
|
||||||
|
local s, e
|
||||||
|
if is_directory(p) then
|
||||||
|
s, e = self:loaddirectory(p, name..item)
|
||||||
|
elseif item:match("%.ans$") then
|
||||||
|
s, e = self:loadfile(p, name..item:gsub("%.ans$", ""))
|
||||||
|
end
|
||||||
|
if not s then return s, e end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- set aliases
|
||||||
|
-- return self
|
||||||
|
loadalias = function(self, name, dest)
|
||||||
|
if type(name) == "table" then
|
||||||
|
for k, v in pairs(name) do
|
||||||
|
self:loadalias(k, v)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.state.aliases[name] = dest
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- define functions
|
||||||
|
-- return self
|
||||||
|
loadfunction = function(self, name, fn)
|
||||||
|
if type(name) == "table" then
|
||||||
|
for k, v in pairs(name) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
for _, variant in ipairs(v) do
|
||||||
|
self:loadfunction(k, variant)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self:loadfunction(k, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not self.state.functions[name] then
|
||||||
|
self.state.functions[name] = {}
|
||||||
|
end
|
||||||
|
if type(fn) == "function" then
|
||||||
|
local info = debug.getinfo(fn)
|
||||||
|
table.insert(self.state.functions[name], {
|
||||||
|
arity = info.isvararg and {info.nparams, math.huge} or info.nparams,
|
||||||
|
value = fn
|
||||||
|
})
|
||||||
|
else
|
||||||
|
table.insert(self.state.functions[name], fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- save/load
|
||||||
|
load = function(self, data)
|
||||||
|
assert(data.anselme_version == anselme.version, ("trying to load a save from Anselme %s but current Anselme version is %s"):format(data.anselme_version, anselme.version))
|
||||||
|
for k, v in pairs(data.variables) do
|
||||||
|
self.state.variables[k] = v
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
save = function(self)
|
||||||
|
return {
|
||||||
|
anselme_version = anselme.version,
|
||||||
|
variables = self.state.variables
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- run code
|
||||||
|
-- return interpreter in case of success
|
||||||
|
-- returns nil, err in case of error
|
||||||
|
run = function(self, expr, namespace, tags)
|
||||||
|
if #self.state.queued_lines > 0 then
|
||||||
|
local r, e = postparse(self.state)
|
||||||
|
if not r then return r, e end
|
||||||
|
end
|
||||||
|
--
|
||||||
|
local err
|
||||||
|
if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state, namespace or "") end
|
||||||
|
if not expr then return expr, err end
|
||||||
|
-- interpreter state
|
||||||
|
local interpreter
|
||||||
|
interpreter = {
|
||||||
|
state = {
|
||||||
|
aliases = self.state.aliases,
|
||||||
|
functions = self.state.functions,
|
||||||
|
variables = setmetatable({}, { __index = self.state.variables }),
|
||||||
|
interpreter = {
|
||||||
|
global_state = self.state,
|
||||||
|
coroutine = coroutine.create(function() return "return", interpreter:run(expr, namespace) end),
|
||||||
|
-- events
|
||||||
|
event_type = nil,
|
||||||
|
event_buffer = nil,
|
||||||
|
-- status
|
||||||
|
running_line = nil,
|
||||||
|
-- choice
|
||||||
|
choice_selected = nil,
|
||||||
|
choice_available = {},
|
||||||
|
-- interrupt
|
||||||
|
interrupt = nil,
|
||||||
|
-- conditions
|
||||||
|
last_condition_success = nil,
|
||||||
|
-- tags
|
||||||
|
tags = tags or {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vm = self
|
||||||
|
}
|
||||||
|
return setmetatable(interpreter, interpreter_methods)
|
||||||
|
end,
|
||||||
|
--- eval code
|
||||||
|
-- return value in case of success
|
||||||
|
-- returns nil, err in case of error
|
||||||
|
eval = function(self, expr, namespace, tags)
|
||||||
|
local interpreter, err = self:run("@", namespace, tags)
|
||||||
|
if not interpreter then return interpreter, err end
|
||||||
|
return interpreter:eval(expr, namespace)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
vm_mt.__index = vm_mt
|
||||||
|
|
||||||
|
--- anselme module
|
||||||
|
return setmetatable(anselme, {
|
||||||
|
__call = function()
|
||||||
|
-- global state
|
||||||
|
local state = {
|
||||||
|
aliases = {
|
||||||
|
-- seen = "👁️",
|
||||||
|
-- checkpoint = "🏁"
|
||||||
|
},
|
||||||
|
functions = {
|
||||||
|
-- [":="] = {
|
||||||
|
-- {
|
||||||
|
-- arity = {3,42}, type = { [1] = "variable" }, check = function, rewrite = function, vararg = 2, mode = "custom",
|
||||||
|
-- value = function(state, exp)
|
||||||
|
-- end -- or paragraph, function, line
|
||||||
|
-- }
|
||||||
|
-- },
|
||||||
|
},
|
||||||
|
variables = {
|
||||||
|
-- foo = {
|
||||||
|
-- type = "number",
|
||||||
|
-- value = 42
|
||||||
|
-- },
|
||||||
|
},
|
||||||
|
queued_lines = {
|
||||||
|
-- { line = line, namespace = "foo" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local vm = setmetatable({ state = state }, vm_mt)
|
||||||
|
vm:loadfunction(stdfuncs)
|
||||||
|
return vm
|
||||||
|
end
|
||||||
|
})
|
||||||
1
init.lua
Normal file
1
init.lua
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
return require((...)..".anselme")
|
||||||
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
|
||||||
153
parser/common.lua
Normal file
153
parser/common.lua
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
local expression
|
||||||
|
|
||||||
|
local escapeCache = {}
|
||||||
|
|
||||||
|
local common
|
||||||
|
common = {
|
||||||
|
--- valid identifier pattern
|
||||||
|
identifier_pattern = "[^%%%/%*%+%-%(%)%!%&%|%=%$%?%>%<%:%{%}%[%]%,]+",
|
||||||
|
--- escape a string to be used as an exact match pattern
|
||||||
|
escape = function(str)
|
||||||
|
if not escapeCache[str] then
|
||||||
|
escapeCache[str] = str:gsub("[^%w]", "%%%1")
|
||||||
|
end
|
||||||
|
return escapeCache[str]
|
||||||
|
end,
|
||||||
|
--- trim a string
|
||||||
|
trim = function(str)
|
||||||
|
return str:match("^%s*(.-)%s*$")
|
||||||
|
end,
|
||||||
|
--- split a string separated by .
|
||||||
|
split = function(str)
|
||||||
|
local address = {}
|
||||||
|
for name in str:gmatch("[^%.]+") do
|
||||||
|
table.insert(address, name)
|
||||||
|
end
|
||||||
|
return address
|
||||||
|
end,
|
||||||
|
--- find a variable/function in a list, going up through the namespace hierarchy
|
||||||
|
find = function(list, namespace, name)
|
||||||
|
local ns = common.split(namespace)
|
||||||
|
for i=#ns, 1, -1 do
|
||||||
|
local fqm = ("%s.%s"):format(table.concat(ns, ".", 1, i), name)
|
||||||
|
if list[fqm] then
|
||||||
|
return list[fqm], fqm
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if list[name] then
|
||||||
|
return list[name], name
|
||||||
|
end
|
||||||
|
return nil, ("can't find %q in namespace %s"):format(name, namespace)
|
||||||
|
end,
|
||||||
|
--- transform an identifier into a clean version
|
||||||
|
format_identifier = function(identifier, state)
|
||||||
|
local r = identifier:gsub("[^%.]+", function(str)
|
||||||
|
str = common.trim(str)
|
||||||
|
return state.aliases[str] or str
|
||||||
|
end)
|
||||||
|
return r
|
||||||
|
end,
|
||||||
|
--- flatten a nested list expression into a list of expressions
|
||||||
|
flatten_list = function(list, t)
|
||||||
|
t = t or {}
|
||||||
|
if list.type == "list" then
|
||||||
|
table.insert(t, list.left)
|
||||||
|
common.flatten_list(list.right, t)
|
||||||
|
else
|
||||||
|
table.insert(t, list)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end,
|
||||||
|
-- * list of strings and expressions
|
||||||
|
-- * nil, err: in case of error
|
||||||
|
parse_text = function(text, state, namespace)
|
||||||
|
local l = {}
|
||||||
|
while text:match("[^%{]+") do
|
||||||
|
local t, e = text:match("^([^%{]*)(.-)$")
|
||||||
|
-- text
|
||||||
|
if t ~= "" then table.insert(l, t) end
|
||||||
|
-- expr
|
||||||
|
if e:match("^{") then
|
||||||
|
local exp, rem = expression(e:gsub("^{", ""), state, namespace)
|
||||||
|
if not exp then return nil, rem end
|
||||||
|
if not rem:match("^%s*}") then return nil, ("expected closing } at end of expression before %q"):format(rem) end
|
||||||
|
table.insert(l, exp)
|
||||||
|
text = rem:match("^%s*}(.*)$")
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return l
|
||||||
|
end,
|
||||||
|
-- find compatible function variant
|
||||||
|
-- * variant: if success
|
||||||
|
-- * nil, err: if error
|
||||||
|
find_function_variant = function(fqm, state, arg, explicit_call)
|
||||||
|
local err = ("function %q variant not found"):format(fqm)
|
||||||
|
local func = state.functions[fqm] or {}
|
||||||
|
local args = arg and common.flatten_list(arg) or {}
|
||||||
|
for _, variant in ipairs(func) do
|
||||||
|
local ok = true
|
||||||
|
local return_type = variant.return_type
|
||||||
|
if variant.arity then
|
||||||
|
local min, max
|
||||||
|
if type(variant.arity) == "table" then
|
||||||
|
min, max = variant.arity[1], variant.arity[2]
|
||||||
|
else
|
||||||
|
min, max = variant.arity, variant.arity
|
||||||
|
end
|
||||||
|
if #args < min or #args > max then
|
||||||
|
if min == max then
|
||||||
|
err = ("function %q expected %s arguments but received %s"):format(fqm, min, #args)
|
||||||
|
else
|
||||||
|
err = ("function %q expected between %s and %s arguments but received %s"):format(fqm, min, max, #args)
|
||||||
|
end
|
||||||
|
ok = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ok and variant.check then
|
||||||
|
local s, e = variant.check(state, args)
|
||||||
|
if not s then
|
||||||
|
err = e or ("function %q variant failed to check arguments"):format(fqm)
|
||||||
|
ok = false
|
||||||
|
end
|
||||||
|
return_type = s == true and return_type or s
|
||||||
|
end
|
||||||
|
if ok and variant.types then
|
||||||
|
for j, t in pairs(variant.types) do
|
||||||
|
if args[j] and args[j].return_type and args[j].return_type ~= t then
|
||||||
|
err = ("function %q expected a %s as argument %s but received a %s"):format(fqm, t, j, args[j].return_type)
|
||||||
|
ok = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ok then
|
||||||
|
if variant.rewrite then
|
||||||
|
local r, e = variant.rewrite(fqm, state, arg, explicit_call)
|
||||||
|
if not r then
|
||||||
|
err = e
|
||||||
|
ok = false
|
||||||
|
end
|
||||||
|
if ok then
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return {
|
||||||
|
type = "function",
|
||||||
|
return_type = return_type,
|
||||||
|
name = fqm,
|
||||||
|
explicit_call = explicit_call,
|
||||||
|
variant = variant,
|
||||||
|
argument = arg
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
package.loaded[...] = common
|
||||||
|
expression = require((...):gsub("common$", "expression"))
|
||||||
|
|
||||||
|
return common
|
||||||
247
parser/expression.lua
Normal file
247
parser/expression.lua
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text
|
||||||
|
|
||||||
|
--- binop priority
|
||||||
|
local binops_prio = {
|
||||||
|
[1] = { ";" },
|
||||||
|
[2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" },
|
||||||
|
[3] = { "," },
|
||||||
|
[4] = { "|", "&" },
|
||||||
|
[5] = { "!=", "=", ">=", "<=", "<", ">" },
|
||||||
|
[6] = { "+", "-" },
|
||||||
|
[7] = { "*", "//", "/", "%" },
|
||||||
|
[8] = {}, -- unary operators
|
||||||
|
[9] = { "^", ":" },
|
||||||
|
[10] = { "." }
|
||||||
|
}
|
||||||
|
-- unop priority
|
||||||
|
local unops_prio = {
|
||||||
|
[1] = {},
|
||||||
|
[2] = {},
|
||||||
|
[3] = {},
|
||||||
|
[4] = {},
|
||||||
|
[5] = {},
|
||||||
|
[6] = {},
|
||||||
|
[7] = {},
|
||||||
|
[8] = { "-", "!" },
|
||||||
|
[9] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
--- parse an expression
|
||||||
|
-- return expr, remaining if success
|
||||||
|
-- returns nil, err if error
|
||||||
|
local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
|
s = s:match("^%s*(.*)$")
|
||||||
|
currentPriority = currentPriority or 0
|
||||||
|
if not operatingOn then
|
||||||
|
-- number
|
||||||
|
if s:match("^%d+%.%d*") or s:match("^%d*%.%d+") or s:match("^%d+") then
|
||||||
|
local d, r = s:match("^(%d*%.%d*)(.*)$")
|
||||||
|
if not d then
|
||||||
|
d, r = s:match("^(%d+)(.*)$")
|
||||||
|
end
|
||||||
|
return expression(r, state, namespace, currentPriority, {
|
||||||
|
type = "number",
|
||||||
|
return_type = "number",
|
||||||
|
value = tonumber(d)
|
||||||
|
})
|
||||||
|
-- string
|
||||||
|
elseif s:match("^%\"[^\"]*%\"") then
|
||||||
|
local d, r = s:match("^%\"([^\"]*)%\"(.*)$")
|
||||||
|
while d:match("\\$") and not d:match("\\\\$") do
|
||||||
|
local nd, nr = r:match("([^\"]*)%\"(.*)$")
|
||||||
|
if not nd then return nil, ("unfinished string near %q"):format(r) end
|
||||||
|
d, r = d:sub(1, -2) .. "\"" .. nd, nr
|
||||||
|
end
|
||||||
|
local l, e = parse_text(tostring(d), state, namespace)
|
||||||
|
if not l then return l, e end
|
||||||
|
return expression(r, state, namespace, currentPriority, {
|
||||||
|
type = "string",
|
||||||
|
return_type = "string",
|
||||||
|
value = l
|
||||||
|
})
|
||||||
|
-- paranthesis
|
||||||
|
elseif s:match("^%b()") then
|
||||||
|
local content, r = s:match("^(%b())(.*)$")
|
||||||
|
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||||
|
local exp, r_paren = expression(content, state, namespace)
|
||||||
|
if not exp then return nil, "invalid expression inside parentheses: "..r_paren end
|
||||||
|
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of parenthesis expression"):format(r_paren) end
|
||||||
|
return expression(r, state, namespace, currentPriority, {
|
||||||
|
type = "parentheses",
|
||||||
|
return_type = exp.return_type,
|
||||||
|
expression = exp
|
||||||
|
})
|
||||||
|
-- list parenthesis
|
||||||
|
elseif s:match("^%b[]") then
|
||||||
|
local content, r = s:match("^(%b[])(.*)$")
|
||||||
|
content = content:gsub("^%[", ""):gsub("%]$", "")
|
||||||
|
local exp
|
||||||
|
if content:match("[^%s]") then
|
||||||
|
local r_paren
|
||||||
|
exp, r_paren = expression(content, state, namespace)
|
||||||
|
if not exp then return nil, "invalid expression inside list parentheses: "..r_paren end
|
||||||
|
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of list parenthesis expression"):format(r_paren) end
|
||||||
|
end
|
||||||
|
return expression(r, state, namespace, currentPriority, {
|
||||||
|
type = "list_parentheses",
|
||||||
|
return_type = "list",
|
||||||
|
expression = exp
|
||||||
|
})
|
||||||
|
-- identifier
|
||||||
|
elseif s:match("^"..identifier_pattern) then
|
||||||
|
local name, r = s:match("^("..identifier_pattern..")(.-)$")
|
||||||
|
name = format_identifier(name, state)
|
||||||
|
-- functions
|
||||||
|
local funcs, ffqm = find(state.functions, namespace, name)
|
||||||
|
if funcs then
|
||||||
|
local args, explicit_call
|
||||||
|
if r:match("^%b()") then
|
||||||
|
explicit_call = true
|
||||||
|
local content, rem = r:match("^(%b())(.*)$")
|
||||||
|
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||||
|
r = rem
|
||||||
|
-- get arguments
|
||||||
|
if content:match("[^%s]") then
|
||||||
|
local err
|
||||||
|
args, err = expression(content, state, namespace)
|
||||||
|
if not args then return args, err end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- find compatible variant
|
||||||
|
local variant, err = find_function_variant(ffqm, state, args, explicit_call)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
|
end
|
||||||
|
-- variables
|
||||||
|
local var, vfqm = find(state.variables, namespace, name)
|
||||||
|
if var then
|
||||||
|
return expression(r, state, namespace, currentPriority, {
|
||||||
|
type = "variable",
|
||||||
|
return_type = var.type ~= "undefined argument" and var.type or nil,
|
||||||
|
name = vfqm
|
||||||
|
})
|
||||||
|
end
|
||||||
|
-- suffix call: detect if prefix is valid variable, suffix call is handled in the binop section below
|
||||||
|
local sname, suffix = name:match("^(.*)(%."..identifier_pattern..")$")
|
||||||
|
if sname then
|
||||||
|
local svar, svfqm = find(state.variables, namespace, sname)
|
||||||
|
if svar then
|
||||||
|
return expression(suffix..r, state, namespace, currentPriority, {
|
||||||
|
type = "variable",
|
||||||
|
return_type = svar.type ~= "undefined argument" and svar.type or nil,
|
||||||
|
name = svfqm
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, ("unknown identifier %q"):format(name)
|
||||||
|
end
|
||||||
|
-- unops
|
||||||
|
for prio, oplist in ipairs(unops_prio) do
|
||||||
|
for _, op in ipairs(oplist) do
|
||||||
|
local escaped = escape(op)
|
||||||
|
if s:match("^"..escaped) then
|
||||||
|
local right, r = expression(s:match("^"..escaped.."(.*)$"), state, namespace, prio)
|
||||||
|
if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end
|
||||||
|
-- find variant
|
||||||
|
local variant, err = find_function_variant(op, state, right, true)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, ("no valid expression before %q"):format(s)
|
||||||
|
else
|
||||||
|
-- binop
|
||||||
|
for prio, oplist in ipairs(binops_prio) do
|
||||||
|
if prio >= currentPriority then
|
||||||
|
for _, op in ipairs(oplist) do
|
||||||
|
local escaped = escape(op)
|
||||||
|
if s:match("^"..escaped) then
|
||||||
|
local sright = s:match("^"..escaped.."(.*)$")
|
||||||
|
-- suffix call
|
||||||
|
if op == "." and sright:match("^"..identifier_pattern) then
|
||||||
|
local name, r = sright:match("^("..identifier_pattern..")(.-)$")
|
||||||
|
name = format_identifier(name, state)
|
||||||
|
local funcs, ffqm = find(state.functions, namespace, name)
|
||||||
|
if funcs then
|
||||||
|
local args, explicit_call
|
||||||
|
if r:match("^%b()") then
|
||||||
|
explicit_call = true
|
||||||
|
local content, rem = r:match("^(%b())(.*)$")
|
||||||
|
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||||
|
r = rem
|
||||||
|
-- get arguments
|
||||||
|
if content:match("[^%s]") then
|
||||||
|
local err
|
||||||
|
args, err = expression(content, state, namespace)
|
||||||
|
if not args then return args, err end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- add first argument
|
||||||
|
if not args then
|
||||||
|
args = operatingOn
|
||||||
|
else
|
||||||
|
args = {
|
||||||
|
type = "list",
|
||||||
|
return_type = "list",
|
||||||
|
left = operatingOn,
|
||||||
|
right = args
|
||||||
|
}
|
||||||
|
end
|
||||||
|
-- find compatible variant
|
||||||
|
local variant, err = find_function_variant(ffqm, state, args, explicit_call)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local right, r = expression(sright, state, namespace, prio)
|
||||||
|
if not right then return nil, ("invalid expression after binop %q: %s"):format(op, r) end
|
||||||
|
-- list constructor
|
||||||
|
if op == "," then
|
||||||
|
return expression(r, state, namespace, currentPriority, {
|
||||||
|
type = "list",
|
||||||
|
return_type = "list",
|
||||||
|
left = operatingOn,
|
||||||
|
right = right
|
||||||
|
})
|
||||||
|
-- normal binop
|
||||||
|
else
|
||||||
|
-- find variant
|
||||||
|
local args = {
|
||||||
|
type = "list",
|
||||||
|
return_type = "list",
|
||||||
|
left = operatingOn,
|
||||||
|
-- wrap in parentheses to avoid appending to argument list if right is a list
|
||||||
|
right = { type = "parentheses", return_type = right.return_type, expression = right }
|
||||||
|
}
|
||||||
|
local variant, err = find_function_variant(op, state, args, true)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- index
|
||||||
|
if s:match("^%b()") then
|
||||||
|
local content, r = s:match("^(%b())(.*)$")
|
||||||
|
-- get arguments (parentheses are kept)
|
||||||
|
local right, r_paren = expression(content, state, namespace)
|
||||||
|
if not right then return right, r_paren end
|
||||||
|
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of index expression"):format(r_paren) end
|
||||||
|
local args = { type = "list", left = operatingOn, right = right }
|
||||||
|
local variant, err = find_function_variant("(", state, args, true)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
|
end
|
||||||
|
-- nothing to operate
|
||||||
|
return operatingOn, s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
package.loaded[...] = expression
|
||||||
|
local common = require((...):gsub("expression$", "common"))
|
||||||
|
identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text
|
||||||
|
|
||||||
|
return expression
|
||||||
70
parser/postparser.lua
Normal file
70
parser/postparser.lua
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
local expression
|
||||||
|
local parse_text
|
||||||
|
|
||||||
|
-- * true: if success
|
||||||
|
-- * nil, error: in case of error
|
||||||
|
local function parse(state)
|
||||||
|
for _, l in ipairs(state.queued_lines) do
|
||||||
|
local line, namespace = l.line, l.namespace
|
||||||
|
-- decorators
|
||||||
|
if line.condition then
|
||||||
|
if line.condition:match("[^%s]") then
|
||||||
|
local exp, rem = expression(line.condition, state, namespace)
|
||||||
|
if not exp then return nil, ("%s; at line %s"):format(rem, line.line) end
|
||||||
|
if rem:match("[^%s]") then return nil, ("expected end of expression before %q in condition decorator; at line %s"):format(rem, line.line) end
|
||||||
|
line.condition = exp
|
||||||
|
else
|
||||||
|
line.condition = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if line.tag then
|
||||||
|
if line.tag:match("[^%s]") then
|
||||||
|
local exp, rem = expression(line.tag, state, namespace)
|
||||||
|
if not exp then return nil, ("%s; at line %s"):format(rem, line.line) end
|
||||||
|
if rem:match("[^%s]") then return nil, ("expected end of expression before %q in condition decorator; at line %s"):format(rem, line.line) end
|
||||||
|
line.tag = exp
|
||||||
|
else
|
||||||
|
line.tag = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- expressions
|
||||||
|
if line.expression then
|
||||||
|
if line.expression:match("[^%s]") then
|
||||||
|
local exp, rem = expression(line.expression, state, namespace)
|
||||||
|
if not exp then return nil, ("%s; at line %s"):format(rem, line.line) end
|
||||||
|
if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at line %s"):format(rem, line.line) end
|
||||||
|
line.expression = exp
|
||||||
|
else
|
||||||
|
line.expression = nil
|
||||||
|
end
|
||||||
|
-- function return type information
|
||||||
|
if line.type == "return" then
|
||||||
|
local variant = line.parent_function.variant
|
||||||
|
local return_type = line.expression.return_type
|
||||||
|
if return_type then
|
||||||
|
if not variant.return_type then
|
||||||
|
variant.return_type = return_type
|
||||||
|
elseif variant.return_type ~= return_type then
|
||||||
|
return nil, ("trying to return a %s in a function that returns a %s; at line %s"):format(return_type, variant.return_type, line.line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- text
|
||||||
|
if line.text then
|
||||||
|
local txt, err = parse_text(line.text, state, namespace)
|
||||||
|
if err then return nil, ("%s; at line %s"):format(err, line.line) end
|
||||||
|
line.text = txt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
state.queued_lines = {}
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
package.loaded[...] = parse
|
||||||
|
expression = require((...):gsub("postparser$", "expression"))
|
||||||
|
local common = require((...):gsub("postparser$", "common"))
|
||||||
|
parse_text = common.parse_text
|
||||||
|
|
||||||
|
--- postparse shit: parse expressions and do variable existence and type checking
|
||||||
|
return parse
|
||||||
332
parser/preparser.lua
Normal file
332
parser/preparser.lua
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
local expression
|
||||||
|
local format_identifier, identifier_pattern
|
||||||
|
local eval
|
||||||
|
|
||||||
|
-- * ast: if success
|
||||||
|
-- * nil, error: in case of error
|
||||||
|
local function parse_line(line, state, namespace)
|
||||||
|
local l = line.content
|
||||||
|
local r = {
|
||||||
|
line = line.line
|
||||||
|
}
|
||||||
|
-- comment
|
||||||
|
if l:match("^%(") then
|
||||||
|
r.type = "comment"
|
||||||
|
r.remove_from_block_ast = true
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
-- decorators
|
||||||
|
while l:match("^..+[~#]") or l:match("^..+§") do
|
||||||
|
-- condition
|
||||||
|
if l:match("^..+%~.-$") then
|
||||||
|
local expr
|
||||||
|
l, expr = l:match("^(.-)%s*%~(.-)$")
|
||||||
|
r.condition = expr
|
||||||
|
-- paragraph
|
||||||
|
elseif l:match("^..+§.-$") then
|
||||||
|
local name
|
||||||
|
l, name = l:match("^(.-)%s*§(.-)$")
|
||||||
|
local fqm = ("%s%s"):format(namespace, format_identifier(name, state))
|
||||||
|
namespace = fqm.."."
|
||||||
|
r.paragraph = true
|
||||||
|
r.parent_function = true
|
||||||
|
r.namespace = fqm.."."
|
||||||
|
r.name = fqm
|
||||||
|
if not state.functions[fqm] then
|
||||||
|
state.functions[fqm] = {
|
||||||
|
{
|
||||||
|
arity = 0,
|
||||||
|
value = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if not state.variables[fqm..".👁️"] then
|
||||||
|
state.variables[fqm..".👁️"] = {
|
||||||
|
type = "number",
|
||||||
|
value = 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(state.functions[fqm], {
|
||||||
|
arity = 0,
|
||||||
|
value = r
|
||||||
|
})
|
||||||
|
end
|
||||||
|
-- tag
|
||||||
|
elseif l:match("^..+%#.-$") then
|
||||||
|
local expr
|
||||||
|
l, expr = l:match("^(.-)%s*%#(.-)$")
|
||||||
|
r.tag = expr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- else-condition & condition
|
||||||
|
if l:match("^~~?") then
|
||||||
|
r.type = l:match("^~~") and "else-condition" or "condition"
|
||||||
|
r.child = true
|
||||||
|
local expr = l:match("^~~?(.*)$")
|
||||||
|
if expr:match("[^%s]") then
|
||||||
|
r.expression = expr
|
||||||
|
else
|
||||||
|
r.expression = "1"
|
||||||
|
end
|
||||||
|
-- choice
|
||||||
|
elseif l:match("^>") then
|
||||||
|
r.type = "choice"
|
||||||
|
r.push_event = "choice"
|
||||||
|
r.child = true
|
||||||
|
r.text = l:match("^>%s*(.-)$")
|
||||||
|
-- function & paragraph
|
||||||
|
elseif l:match("^%$") or l:match("^§") then -- § is a 2-bytes caracter, DO NOT USE LUA PATTERN OPERATORS as they operate on single bytes
|
||||||
|
r.type = l:match("^%$") and "function" or "paragraph"
|
||||||
|
r.child = true
|
||||||
|
local fqm = ("%s%s"):format(namespace, format_identifier(l:match("^%$(.*)$") or l:match("^§(.*)$"), state))
|
||||||
|
-- get params
|
||||||
|
r.params = {}
|
||||||
|
if r.type == "function" and fqm:match("%b()$") then
|
||||||
|
local content
|
||||||
|
fqm, content = fqm:match("^(.-)(%b())$")
|
||||||
|
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||||
|
for param in content:gmatch("[^%,]+") do
|
||||||
|
table.insert(r.params, format_identifier(("%s.%s"):format(fqm, param), state))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local arity, vararg = #r.params, nil
|
||||||
|
if arity > 0 and r.params[arity]:match("%.%.%.$") then -- varargs
|
||||||
|
r.params[arity] = r.params[arity]:match("^(.*)%.%.%.$")
|
||||||
|
vararg = arity
|
||||||
|
arity = { arity-1, math.huge }
|
||||||
|
end
|
||||||
|
-- store parent function and run paragraph when line is read
|
||||||
|
if r.type == "paragraph" then
|
||||||
|
r.paragraph = true
|
||||||
|
r.parent_function = true
|
||||||
|
end
|
||||||
|
-- don't keep function node in block AST
|
||||||
|
if r.type == "function" then
|
||||||
|
r.remove_from_block_ast = true
|
||||||
|
if not state.variables[fqm..".🏁"] then
|
||||||
|
state.variables[fqm..".🏁"] = {
|
||||||
|
type = "string",
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- define function and variables
|
||||||
|
r.namespace = fqm.."."
|
||||||
|
r.name = fqm
|
||||||
|
if state.variables[fqm] then return nil, ("trying to define %s %s, but a variable with the same name exists; at line %s"):format(r.type, fqm, line.line) end
|
||||||
|
r.variant = {
|
||||||
|
arity = arity,
|
||||||
|
types = {},
|
||||||
|
vararg = vararg,
|
||||||
|
value = r
|
||||||
|
}
|
||||||
|
if not state.functions[fqm] then
|
||||||
|
state.functions[fqm] = { r.variant }
|
||||||
|
if not state.variables[fqm..".👁️"] then
|
||||||
|
state.variables[fqm..".👁️"] = {
|
||||||
|
type = "number",
|
||||||
|
value = 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- check for arity conflict
|
||||||
|
for _, variant in ipairs(state.functions[fqm]) do
|
||||||
|
local vmin, vmax = 0, math.huge
|
||||||
|
if type(variant.arity) == "table" then
|
||||||
|
vmin, vmax = variant.arity[1], variant.arity[2]
|
||||||
|
elseif variant.arity then
|
||||||
|
vmin, vmax = variant.arity, variant.arity
|
||||||
|
end
|
||||||
|
local min, max = 0, math.huge
|
||||||
|
if type(r.variant.arity) == "table" then
|
||||||
|
min, max = r.variant.arity[1], r.variant.arity[2]
|
||||||
|
elseif r.variant.arity then
|
||||||
|
min, max = variant.arity, r.variant.arity
|
||||||
|
end
|
||||||
|
if min == vmin and max == vmax then
|
||||||
|
return nil, ("trying to define %s %s with arity [%s;%s], but another function with the arity exist; at line %s"):format(r.type, fqm, min, max, line.line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- add
|
||||||
|
table.insert(state.functions[fqm], r.variant)
|
||||||
|
end
|
||||||
|
-- set type check information
|
||||||
|
for i, param in ipairs(r.params) do
|
||||||
|
if not state.variables[param] then
|
||||||
|
state.variables[param] = {
|
||||||
|
type = "undefined argument",
|
||||||
|
value = { r.variant, i }
|
||||||
|
}
|
||||||
|
elseif state.variables[param].type ~= "undefined argument" then
|
||||||
|
r.variant.types[i] = state.variables[param].type
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- definition
|
||||||
|
elseif l:match("^:") then
|
||||||
|
r.type = "definition"
|
||||||
|
r.remove_from_block_ast = true
|
||||||
|
local exp, rem = expression(l:match("^:(.*)$"), state, namespace) -- expression parsing is done directly to get type information
|
||||||
|
if not exp then return nil, ("%s; at line %s"):format(rem, line.line) end
|
||||||
|
local fqm = ("%s%s"):format(namespace, format_identifier(rem, state))
|
||||||
|
if state.functions[fqm] then return nil, ("trying to define variable %s, but a function with the same name exists; at line %s"):format(fqm, line.line) end
|
||||||
|
if not state.variables[fqm] or state.variables[fqm].type == "undefined argument" then
|
||||||
|
local v, e = eval(state, exp)
|
||||||
|
if not v then return v, e end
|
||||||
|
-- update function typecheck information
|
||||||
|
if state.variables[fqm] and state.variables[fqm].type == "undefined argument" then
|
||||||
|
local und = state.variables[fqm].value
|
||||||
|
und[1].types[und[2]] = v.type
|
||||||
|
end
|
||||||
|
state.variables[fqm] = v
|
||||||
|
elseif state.variables[fqm].type ~= exp.type then
|
||||||
|
return nil, ("trying to define variable %s of type %s but a it is already defined with type %s; at line %s"):format(fqm, exp.type, state.variables[fqm].type, line.line)
|
||||||
|
end
|
||||||
|
-- tag
|
||||||
|
elseif l:match("^%#") then
|
||||||
|
r.type = "tag"
|
||||||
|
r.child = true
|
||||||
|
r.expression = l:match("^%#(.*)$")
|
||||||
|
-- return
|
||||||
|
elseif l:match("^%@") then
|
||||||
|
r.type = "return"
|
||||||
|
r.parent_function = true
|
||||||
|
r.expression = l:match("^%@(.*)$")
|
||||||
|
-- text
|
||||||
|
elseif l:match("[^%s]") then
|
||||||
|
r.type = "text"
|
||||||
|
r.push_event = "text"
|
||||||
|
r.text = l
|
||||||
|
-- flush events
|
||||||
|
else
|
||||||
|
r.type = "flush_events"
|
||||||
|
end
|
||||||
|
if not r.type then return nil, ("unknown line %s type"):format(line.line) end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
-- * block: in case of success
|
||||||
|
-- * nil, err: in case of error
|
||||||
|
local function parse_block(indented, state, namespace, parent_function, last_event)
|
||||||
|
local block = { type = "block" }
|
||||||
|
local lastLine -- last line AST
|
||||||
|
for i, l in ipairs(indented) do
|
||||||
|
-- parsable line
|
||||||
|
if l.content then
|
||||||
|
local ast, err = parse_line(l, state, namespace)
|
||||||
|
if err then return nil, err end
|
||||||
|
lastLine = ast
|
||||||
|
-- store parent function
|
||||||
|
if ast.parent_function then ast.parent_function = parent_function end
|
||||||
|
-- add to block AST
|
||||||
|
if not ast.remove_from_block_ast then
|
||||||
|
ast.parent_block = block
|
||||||
|
-- insert flush on event type change
|
||||||
|
if ast.type == "flush" then last_event = nil end
|
||||||
|
if ast.push_event then
|
||||||
|
if last_event and ast.push_event ~= last_event then
|
||||||
|
table.insert(block, { line = l.line, type = "flush_events" })
|
||||||
|
end
|
||||||
|
last_event = ast.push_event
|
||||||
|
end
|
||||||
|
-- add ast node
|
||||||
|
ast.parent_position = #block+1
|
||||||
|
if ast.replace_with then
|
||||||
|
if indented[i+1].content then
|
||||||
|
table.insert(indented, i+1, { content = ast.replace_with, line = l.line })
|
||||||
|
else
|
||||||
|
table.insert(indented, i+2, { content = ast.replace_with, line = l.line }) -- if line has children
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(block, ast)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- add child
|
||||||
|
if ast.child then ast.child = { type = "block", parent_line = ast } end
|
||||||
|
-- queue in expression evalution
|
||||||
|
table.insert(state.queued_lines, { namespace = ast.namespace or namespace, line = ast })
|
||||||
|
-- indented (ignore block comments)
|
||||||
|
elseif lastLine.type ~= "comment" then
|
||||||
|
if not lastLine.child then
|
||||||
|
return nil, ("line %s (%s) can't have children"):format(lastLine.line, lastLine.type)
|
||||||
|
else
|
||||||
|
local r, e = parse_block(l, state, lastLine.namespace or namespace, lastLine.type == "function" and lastLine or parent_function, last_event)
|
||||||
|
if not r then return r, e end
|
||||||
|
r.parent_line = lastLine
|
||||||
|
lastLine.child = r
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return block
|
||||||
|
end
|
||||||
|
|
||||||
|
--- returns the nested list of lines {content="", line=1}, grouped by indentation
|
||||||
|
-- multiple empty lines are merged
|
||||||
|
-- * list, last line
|
||||||
|
local function parse_indent(lines, i, indentLevel, insert_empty_line)
|
||||||
|
i = i or 1
|
||||||
|
indentLevel = indentLevel or 0
|
||||||
|
local indented = {}
|
||||||
|
while i <= #lines do
|
||||||
|
if lines[i]:match("[^%s]") then
|
||||||
|
local indent, line = lines[i]:match("^(%s*)(.*)$")
|
||||||
|
if #indent == indentLevel then
|
||||||
|
if insert_empty_line then
|
||||||
|
table.insert(indented, { content = "", line = insert_empty_line })
|
||||||
|
insert_empty_line = false
|
||||||
|
end
|
||||||
|
table.insert(indented, { content = line, line = i })
|
||||||
|
elseif #indent > indentLevel then
|
||||||
|
local t
|
||||||
|
t, i = parse_indent(lines, i, #indent, insert_empty_line)
|
||||||
|
table.insert(indented, t)
|
||||||
|
else
|
||||||
|
return indented, i-1
|
||||||
|
end
|
||||||
|
elseif not insert_empty_line then
|
||||||
|
insert_empty_line = i
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return indented, i-1
|
||||||
|
end
|
||||||
|
|
||||||
|
--- return the list of raw lines of s
|
||||||
|
local function parse_lines(s)
|
||||||
|
local lines = {}
|
||||||
|
for l in (s.."\n"):gmatch("(.-)\n") do
|
||||||
|
table.insert(lines, l)
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
--- preparse shit: create AST structure, define variables and functions, but don't parse expression or perform any type checking
|
||||||
|
-- (wait for other files to be parsed before doing this with postparse)
|
||||||
|
-- * state: in case of success
|
||||||
|
-- * nil, err: in case of error
|
||||||
|
local function parse(state, s, name)
|
||||||
|
-- parse lines
|
||||||
|
local lines = parse_lines(s)
|
||||||
|
local indented = parse_indent(lines)
|
||||||
|
-- wrap in named function if neccessary
|
||||||
|
if name ~= "" then
|
||||||
|
if not name:match("^"..identifier_pattern.."$") then
|
||||||
|
return nil, ("invalid function name %q"):format(name)
|
||||||
|
end
|
||||||
|
indented = {
|
||||||
|
{ content = "$ "..name, line = 0 },
|
||||||
|
indented
|
||||||
|
}
|
||||||
|
end
|
||||||
|
-- parse
|
||||||
|
local root, err = parse_block(indented, state, "")
|
||||||
|
if not root then return nil, err end
|
||||||
|
return state
|
||||||
|
end
|
||||||
|
|
||||||
|
package.loaded[...] = parse
|
||||||
|
expression = require((...):gsub("preparser$", "expression"))
|
||||||
|
local common = require((...):gsub("preparser$", "common"))
|
||||||
|
format_identifier, identifier_pattern = common.format_identifier, common.identifier_pattern
|
||||||
|
eval = require((...):gsub("parser%.preparser$", "interpreter.expression"))
|
||||||
|
|
||||||
|
return parse
|
||||||
37
run.lua
37
run.lua
|
|
@ -1,37 +0,0 @@
|
||||||
require("candran").setup()
|
|
||||||
|
|
||||||
local vm = require("anselme")()
|
|
||||||
|
|
||||||
vm:loaddirectory(".")
|
|
||||||
vm:loadfile("test.ans")
|
|
||||||
|
|
||||||
print(require("inspect")(vm.state))
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local e, d = vm:step()
|
|
||||||
if e == "text" then
|
|
||||||
for _, t in ipairs(d) do
|
|
||||||
print(t.text)
|
|
||||||
for k,v in pairs(t.tags) do
|
|
||||||
print("> "..tostring(k)..": "..tostring(v))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
print("-----")
|
|
||||||
elseif e == "choice" then
|
|
||||||
for i, c in ipairs(d) do
|
|
||||||
print(tostring(i)..": "..c.text)
|
|
||||||
for k,v in pairs(c.tags) do
|
|
||||||
print("> "..tostring(k)..": "..tostring(v))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local choice
|
|
||||||
repeat
|
|
||||||
choice = tonumber(io.read("*l"))
|
|
||||||
until choice ~= nil and choice > 0 and choice <= #d
|
|
||||||
vm:choose(choice)
|
|
||||||
elseif e == "end" then
|
|
||||||
break
|
|
||||||
else
|
|
||||||
error("unknown event ("..tostring(e)..")")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
334
stdlib/functions.lua
Normal file
334
stdlib/functions.lua
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
local truthy, eval, find_function_variant, anselme
|
||||||
|
|
||||||
|
local function rewrite_assignement(fqm, state, arg, explicit_call)
|
||||||
|
local op, e = find_function_variant(fqm:match("^(.*)%=$"), state, arg, true)
|
||||||
|
if not op then return op, e end
|
||||||
|
local ass, err = find_function_variant(":=", state, { type = "list", left = arg.left, right = op }, explicit_call)
|
||||||
|
if not ass then return ass, err end
|
||||||
|
return ass
|
||||||
|
end
|
||||||
|
|
||||||
|
local functions
|
||||||
|
functions = {
|
||||||
|
-- discard left
|
||||||
|
[";"] = {
|
||||||
|
{
|
||||||
|
arity = 2, mode = "raw",
|
||||||
|
value = function(a, b) return b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- assignement
|
||||||
|
[":="] = {
|
||||||
|
{
|
||||||
|
arity = 2, mode = "custom",
|
||||||
|
check = function(state, args)
|
||||||
|
local left, right = args[1], args[2]
|
||||||
|
if left.type ~= "variable" then
|
||||||
|
return nil, ("assignement expected a variable as a left argument but received a %s"):format(left.type)
|
||||||
|
end
|
||||||
|
if left.return_type and right.return_type and left.return_type ~= right.return_type then
|
||||||
|
return nil, ("trying to assign a %s value to a %s variable"):format(right.return_type, left.return_type)
|
||||||
|
end
|
||||||
|
return right.return_type or true
|
||||||
|
end,
|
||||||
|
value = function(state, exp)
|
||||||
|
local arg = exp.argument
|
||||||
|
local name = arg.left.name
|
||||||
|
local right, righte = eval(state, arg.right)
|
||||||
|
if not right then return right, righte end
|
||||||
|
state.variables[name] = right
|
||||||
|
return right
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["+="] = {
|
||||||
|
{ rewrite = rewrite_assignement }
|
||||||
|
},
|
||||||
|
["-="] = {
|
||||||
|
{ rewrite = rewrite_assignement }
|
||||||
|
},
|
||||||
|
["*="] = {
|
||||||
|
{ rewrite = rewrite_assignement }
|
||||||
|
},
|
||||||
|
["/="] = {
|
||||||
|
{ rewrite = rewrite_assignement }
|
||||||
|
},
|
||||||
|
["//="] = {
|
||||||
|
{ rewrite = rewrite_assignement }
|
||||||
|
},
|
||||||
|
["%="] = {
|
||||||
|
{ rewrite = rewrite_assignement }
|
||||||
|
},
|
||||||
|
["^="] = {
|
||||||
|
{ rewrite = rewrite_assignement }
|
||||||
|
},
|
||||||
|
-- comparaison
|
||||||
|
["="] = {
|
||||||
|
{
|
||||||
|
arity = 2, return_type = "number", mode = "raw",
|
||||||
|
value = function(a, b)
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = (a.type == b.type and a.value == b.value) and 1 or 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["!="] = {
|
||||||
|
{
|
||||||
|
arity = 2, return_type = "number", mode = "raw",
|
||||||
|
value = function(a, b)
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = (a.type == b.type and a.value == b.value) and 0 or 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[">"] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a > b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["<"] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a < b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[">="] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a >= b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["<="] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a <= b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- arithmetic
|
||||||
|
["+"] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a + b end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arity = 2, types = { "string", "string" }, return_type = "string",
|
||||||
|
value = function(a, b) return a .. b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["-"] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a - b end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arity = 1, types = { "number" }, return_type = "number",
|
||||||
|
value = function(a) return -a end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["*"] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a * b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["/"] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a / b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["//"] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return math.floor(a / b) end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["^"] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b) return a ^ b end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- boolean
|
||||||
|
["!"] = {
|
||||||
|
{
|
||||||
|
arity = 1, return_type = "number", mode = "raw",
|
||||||
|
value = function(a)
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = truthy(a) and 0 or 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["&"] = {
|
||||||
|
{
|
||||||
|
arity = 2, return_type = "number", mode = "custom",
|
||||||
|
value = function(state, exp)
|
||||||
|
local left, lefte = eval(state, exp.left)
|
||||||
|
if not left then return left, lefte end
|
||||||
|
if truthy(left) then
|
||||||
|
local right, righte = eval(state, exp.right)
|
||||||
|
if not right then return right, righte end
|
||||||
|
if truthy(right) then
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["|"] = {
|
||||||
|
{
|
||||||
|
arity = 2, return_type = "number", mode = "raw",
|
||||||
|
value = function(state, exp)
|
||||||
|
local left, lefte = eval(state, exp.left)
|
||||||
|
if not left then return left, lefte end
|
||||||
|
if truthy(left) then
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local right, righte = eval(state, exp.right)
|
||||||
|
if not right then return right, righte end
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = truthy(right) and 1 or 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- pair
|
||||||
|
[":"] = {
|
||||||
|
{
|
||||||
|
arity = 2, return_type = "pair", mode = "raw",
|
||||||
|
value = function(a, b)
|
||||||
|
return {
|
||||||
|
type = "pair",
|
||||||
|
value = { a, b }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- index
|
||||||
|
["("] = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "list", "number" }, mode = "raw",
|
||||||
|
value = function(a, b)
|
||||||
|
return a.value[b.value] or { type = "nil", value = nil }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- list methods
|
||||||
|
len = {
|
||||||
|
{
|
||||||
|
arity = 1, types = { "list" }, return_type = "number", mode = "raw", -- raw to count pairs in the list
|
||||||
|
value = function(a)
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = #a.value
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
insert = {
|
||||||
|
{
|
||||||
|
arity = 2, types = { "list" }, return_type = "list", mode = "raw",
|
||||||
|
value = function(a, v)
|
||||||
|
table.insert(a.value, v)
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arity = 3, types = { "list", "number" }, return_type = "list", mode = "raw",
|
||||||
|
value = function(a, k, v)
|
||||||
|
table.insert(a.value, k.value, v)
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove = {
|
||||||
|
{
|
||||||
|
arity = 1, types = { "list" }, return_type = "list", mode = "raw",
|
||||||
|
value = function(a)
|
||||||
|
table.remove(a.value)
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arity = 2, types = { "list", "number" }, return_type = "list", mode = "raw",
|
||||||
|
value = function(a, k)
|
||||||
|
table.remove(a.value, k.value)
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rand = {
|
||||||
|
{
|
||||||
|
arity = 0, return_type = "number",
|
||||||
|
value = function()
|
||||||
|
return math.random()
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arity = 1, types = { "number" }, return_type = "number",
|
||||||
|
value = function(a)
|
||||||
|
return math.random(a)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arity = 2, types = { "number", "number" }, return_type = "number",
|
||||||
|
value = function(a, b)
|
||||||
|
return math.random(a, b)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cycle = function(...)
|
||||||
|
local l = {...}
|
||||||
|
local f, fseen = l[1], assert(anselme.running:eval(l[1]..".👁️", anselme.running:current_namespace()))
|
||||||
|
for j=2, #l do
|
||||||
|
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
|
||||||
|
if seen < fseen then
|
||||||
|
f = l[j]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return anselme.running:run(f, anselme.running:current_namespace())
|
||||||
|
end,
|
||||||
|
random = function(...)
|
||||||
|
local l = {...}
|
||||||
|
return anselme.running:run(l[math.random(1, #l)], anselme.running:current_namespace())
|
||||||
|
end,
|
||||||
|
next = function(...)
|
||||||
|
local l = {...}
|
||||||
|
local f = l[#l]
|
||||||
|
for j=1, #l-1 do
|
||||||
|
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
|
||||||
|
if seen == 0 then
|
||||||
|
f = l[j]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return anselme.running:run(f, anselme.running:current_namespace())
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
package.loaded[...] = functions
|
||||||
|
truthy = require((...):gsub("stdlib%.functions$", "interpreter.common")).truthy
|
||||||
|
eval = require((...):gsub("stdlib%.functions$", "interpreter.expression"))
|
||||||
|
find_function_variant = require((...):gsub("stdlib%.functions$", "parser.common")).find_function_variant
|
||||||
|
anselme = require((...):gsub("stdlib%.functions$", "anselme"))
|
||||||
|
|
||||||
|
return functions
|
||||||
140
stdlib/types.lua
Normal file
140
stdlib/types.lua
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
local format, to_lua, from_lua
|
||||||
|
|
||||||
|
local types = {}
|
||||||
|
types.lua = {
|
||||||
|
["nil"] = {
|
||||||
|
to_anselme = function(val)
|
||||||
|
return {
|
||||||
|
type = "nil",
|
||||||
|
value = nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
boolean = {
|
||||||
|
to_anselme = function(val)
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = val and 1 or 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
number = {
|
||||||
|
to_anselme = function(val)
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = val
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
string = {
|
||||||
|
to_anselme = function(val)
|
||||||
|
return {
|
||||||
|
type = "string",
|
||||||
|
value = val
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
table = {
|
||||||
|
to_anselme = function(val)
|
||||||
|
local l = {}
|
||||||
|
for _, v in ipairs(val) do
|
||||||
|
local r, e = from_lua(v)
|
||||||
|
if not r then return r, e end
|
||||||
|
table.insert(l, r)
|
||||||
|
end
|
||||||
|
for k, v in pairs(val) do
|
||||||
|
if not l[k] then
|
||||||
|
local kv, ke = from_lua(k)
|
||||||
|
if not k then return k, ke end
|
||||||
|
local vv, ve = from_lua(v)
|
||||||
|
if not v then return v, ve end
|
||||||
|
table.insert(l, {
|
||||||
|
type = "pair",
|
||||||
|
value = { kv, vv }
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
type = "list",
|
||||||
|
value = val
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
types.anselme = {
|
||||||
|
["nil"] = {
|
||||||
|
format = function()
|
||||||
|
return ""
|
||||||
|
end,
|
||||||
|
to_lua = function()
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
},
|
||||||
|
number = {
|
||||||
|
format = function(val)
|
||||||
|
return tostring(val)
|
||||||
|
end,
|
||||||
|
to_lua = function(val)
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
},
|
||||||
|
string = {
|
||||||
|
format = function(val)
|
||||||
|
return tostring(val)
|
||||||
|
end,
|
||||||
|
to_lua = function(val)
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
},
|
||||||
|
list = {
|
||||||
|
format = function(val)
|
||||||
|
local l = {}
|
||||||
|
for _, v in ipairs(val) do
|
||||||
|
local s, e = format(v)
|
||||||
|
if not s then return s, e end
|
||||||
|
table.insert(l, s)
|
||||||
|
end
|
||||||
|
return ("[%s]"):format(table.concat(l, ", "))
|
||||||
|
end,
|
||||||
|
to_lua = function(val)
|
||||||
|
local l = {}
|
||||||
|
for _, v in ipairs(val) do
|
||||||
|
if v.type == "pair" then
|
||||||
|
local k, ke = to_lua(v.value[1])
|
||||||
|
if not k then return k, ke end
|
||||||
|
local x, xe = to_lua(v.value[2])
|
||||||
|
if not x then return x, xe end
|
||||||
|
l[k] = x
|
||||||
|
else
|
||||||
|
local s, e = to_lua(v)
|
||||||
|
if not s then return s, e end
|
||||||
|
table.insert(l, s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return l
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
pair = {
|
||||||
|
format = function(val)
|
||||||
|
local k, ke = format(val[1])
|
||||||
|
if not k then return k, ke end
|
||||||
|
local v, ve = format(val[2])
|
||||||
|
if not v then return v, ve end
|
||||||
|
return ("%s:%s"):format(k, v)
|
||||||
|
end,
|
||||||
|
to_lua = function(val)
|
||||||
|
local k, ke = to_lua(val[1])
|
||||||
|
if not k then return k, ke end
|
||||||
|
local v, ve = to_lua(val[2])
|
||||||
|
if not v then return v, ve end
|
||||||
|
return { [k] = v }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package.loaded[...] = types
|
||||||
|
local common = require((...):gsub("stdlib%.types$", "interpreter.common"))
|
||||||
|
format, to_lua, from_lua = common.format, common.to_lua, common.from_lua
|
||||||
|
|
||||||
|
return types
|
||||||
8
test.ans
8
test.ans
|
|
@ -1,5 +1,3 @@
|
||||||
~
|
$ f(a, b)
|
||||||
hey
|
|
||||||
~ test yep
|
$ f(x)
|
||||||
~
|
|
||||||
lol
|
|
||||||
|
|
|
||||||
334
test/inspect.lua
Normal file
334
test/inspect.lua
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
local inspect ={
|
||||||
|
_VERSION = 'inspect.lua 3.1.0',
|
||||||
|
_URL = 'http://github.com/kikito/inspect.lua',
|
||||||
|
_DESCRIPTION = 'human-readable representations of tables',
|
||||||
|
_LICENSE = [[
|
||||||
|
MIT LICENSE
|
||||||
|
|
||||||
|
Copyright (c) 2013 Enrique García Cota
|
||||||
|
|
||||||
|
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 tostring = tostring
|
||||||
|
|
||||||
|
inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
|
||||||
|
inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})
|
||||||
|
|
||||||
|
local function rawpairs(t)
|
||||||
|
return next, t, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apostrophizes the string if it has quotes, but not aphostrophes
|
||||||
|
-- Otherwise, it returns a regular quoted string
|
||||||
|
local function smartQuote(str)
|
||||||
|
if str:match('"') and not str:match("'") then
|
||||||
|
return "'" .. str .. "'"
|
||||||
|
end
|
||||||
|
return '"' .. str:gsub('"', '\\"') .. '"'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- \a => '\\a', \0 => '\\0', 31 => '\31'
|
||||||
|
local shortControlCharEscapes = {
|
||||||
|
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
|
||||||
|
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v"
|
||||||
|
}
|
||||||
|
local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031
|
||||||
|
for i=0, 31 do
|
||||||
|
local ch = string.char(i)
|
||||||
|
if not shortControlCharEscapes[ch] then
|
||||||
|
shortControlCharEscapes[ch] = "\\"..i
|
||||||
|
longControlCharEscapes[ch] = string.format("\\%03d", i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function escape(str)
|
||||||
|
return (str:gsub("\\", "\\\\")
|
||||||
|
:gsub("(%c)%f[0-9]", longControlCharEscapes)
|
||||||
|
:gsub("%c", shortControlCharEscapes))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isIdentifier(str)
|
||||||
|
return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isSequenceKey(k, sequenceLength)
|
||||||
|
return type(k) == 'number'
|
||||||
|
and 1 <= k
|
||||||
|
and k <= sequenceLength
|
||||||
|
and math.floor(k) == k
|
||||||
|
end
|
||||||
|
|
||||||
|
local defaultTypeOrders = {
|
||||||
|
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
|
||||||
|
['function'] = 5, ['userdata'] = 6, ['thread'] = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
local function sortKeys(a, b)
|
||||||
|
local ta, tb = type(a), type(b)
|
||||||
|
|
||||||
|
-- strings and numbers are sorted numerically/alphabetically
|
||||||
|
if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
|
||||||
|
|
||||||
|
local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
|
||||||
|
-- Two default types are compared according to the defaultTypeOrders table
|
||||||
|
if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
|
||||||
|
elseif dta then return true -- default types before custom ones
|
||||||
|
elseif dtb then return false -- custom types after default ones
|
||||||
|
end
|
||||||
|
|
||||||
|
-- custom types are sorted out alphabetically
|
||||||
|
return ta < tb
|
||||||
|
end
|
||||||
|
|
||||||
|
-- For implementation reasons, the behavior of rawlen & # is "undefined" when
|
||||||
|
-- tables aren't pure sequences. So we implement our own # operator.
|
||||||
|
local function getSequenceLength(t)
|
||||||
|
local len = 1
|
||||||
|
local v = rawget(t,len)
|
||||||
|
while v ~= nil do
|
||||||
|
len = len + 1
|
||||||
|
v = rawget(t,len)
|
||||||
|
end
|
||||||
|
return len - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getNonSequentialKeys(t)
|
||||||
|
local keys, keysLength = {}, 0
|
||||||
|
local sequenceLength = getSequenceLength(t)
|
||||||
|
for k,_ in rawpairs(t) do
|
||||||
|
if not isSequenceKey(k, sequenceLength) then
|
||||||
|
keysLength = keysLength + 1
|
||||||
|
keys[keysLength] = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(keys, sortKeys)
|
||||||
|
return keys, keysLength, sequenceLength
|
||||||
|
end
|
||||||
|
|
||||||
|
local function countTableAppearances(t, tableAppearances)
|
||||||
|
tableAppearances = tableAppearances or {}
|
||||||
|
|
||||||
|
if type(t) == 'table' then
|
||||||
|
if not tableAppearances[t] then
|
||||||
|
tableAppearances[t] = 1
|
||||||
|
for k,v in rawpairs(t) do
|
||||||
|
countTableAppearances(k, tableAppearances)
|
||||||
|
countTableAppearances(v, tableAppearances)
|
||||||
|
end
|
||||||
|
countTableAppearances(getmetatable(t), tableAppearances)
|
||||||
|
else
|
||||||
|
tableAppearances[t] = tableAppearances[t] + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tableAppearances
|
||||||
|
end
|
||||||
|
|
||||||
|
local copySequence = function(s)
|
||||||
|
local copy, len = {}, #s
|
||||||
|
for i=1, len do copy[i] = s[i] end
|
||||||
|
return copy, len
|
||||||
|
end
|
||||||
|
|
||||||
|
local function makePath(path, ...)
|
||||||
|
local keys = {...}
|
||||||
|
local newPath, len = copySequence(path)
|
||||||
|
for i=1, #keys do
|
||||||
|
newPath[len + i] = keys[i]
|
||||||
|
end
|
||||||
|
return newPath
|
||||||
|
end
|
||||||
|
|
||||||
|
local function processRecursive(process, item, path, visited)
|
||||||
|
if item == nil then return nil end
|
||||||
|
if visited[item] then return visited[item] end
|
||||||
|
|
||||||
|
local processed = process(item, path)
|
||||||
|
if type(processed) == 'table' then
|
||||||
|
local processedCopy = {}
|
||||||
|
visited[item] = processedCopy
|
||||||
|
local processedKey
|
||||||
|
|
||||||
|
for k,v in rawpairs(processed) do
|
||||||
|
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
|
||||||
|
if processedKey ~= nil then
|
||||||
|
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
|
||||||
|
if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field
|
||||||
|
setmetatable(processedCopy, mt)
|
||||||
|
processed = processedCopy
|
||||||
|
end
|
||||||
|
return processed
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
|
||||||
|
local Inspector = {}
|
||||||
|
local Inspector_mt = {__index = Inspector}
|
||||||
|
|
||||||
|
function Inspector:puts(...)
|
||||||
|
local args = {...}
|
||||||
|
local buffer = self.buffer
|
||||||
|
local len = #buffer
|
||||||
|
for i=1, #args do
|
||||||
|
len = len + 1
|
||||||
|
buffer[len] = args[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inspector:down(f)
|
||||||
|
self.level = self.level + 1
|
||||||
|
f()
|
||||||
|
self.level = self.level - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inspector:tabify()
|
||||||
|
self:puts(self.newline, string.rep(self.indent, self.level))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inspector:alreadyVisited(v)
|
||||||
|
return self.ids[v] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inspector:getId(v)
|
||||||
|
local id = self.ids[v]
|
||||||
|
if not id then
|
||||||
|
local tv = type(v)
|
||||||
|
id = (self.maxIds[tv] or 0) + 1
|
||||||
|
self.maxIds[tv] = id
|
||||||
|
self.ids[v] = id
|
||||||
|
end
|
||||||
|
return tostring(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inspector:putKey(k)
|
||||||
|
if isIdentifier(k) then return self:puts(k) end
|
||||||
|
self:puts("[")
|
||||||
|
self:putValue(k)
|
||||||
|
self:puts("]")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inspector:putTable(t)
|
||||||
|
if t == inspect.KEY or t == inspect.METATABLE then
|
||||||
|
self:puts(tostring(t))
|
||||||
|
elseif self:alreadyVisited(t) then
|
||||||
|
self:puts('<table ', self:getId(t), '>')
|
||||||
|
elseif self.level >= self.depth then
|
||||||
|
self:puts('{...}')
|
||||||
|
else
|
||||||
|
if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
|
||||||
|
|
||||||
|
local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t)
|
||||||
|
local mt = getmetatable(t)
|
||||||
|
|
||||||
|
self:puts('{')
|
||||||
|
self:down(function()
|
||||||
|
local count = 0
|
||||||
|
for i=1, sequenceLength do
|
||||||
|
if count > 0 then self:puts(',') end
|
||||||
|
self:puts(' ')
|
||||||
|
self:putValue(t[i])
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=1, nonSequentialKeysLength do
|
||||||
|
local k = nonSequentialKeys[i]
|
||||||
|
if count > 0 then self:puts(',') end
|
||||||
|
self:tabify()
|
||||||
|
self:putKey(k)
|
||||||
|
self:puts(' = ')
|
||||||
|
self:putValue(t[k])
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(mt) == 'table' then
|
||||||
|
if count > 0 then self:puts(',') end
|
||||||
|
self:tabify()
|
||||||
|
self:puts('<metatable> = ')
|
||||||
|
self:putValue(mt)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if nonSequentialKeysLength > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing }
|
||||||
|
self:tabify()
|
||||||
|
elseif sequenceLength > 0 then -- array tables have one extra space before closing }
|
||||||
|
self:puts(' ')
|
||||||
|
end
|
||||||
|
|
||||||
|
self:puts('}')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inspector:putValue(v)
|
||||||
|
local tv = type(v)
|
||||||
|
|
||||||
|
if tv == 'string' then
|
||||||
|
self:puts(smartQuote(escape(v)))
|
||||||
|
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
|
||||||
|
tv == 'cdata' or tv == 'ctype' then
|
||||||
|
self:puts(tostring(v))
|
||||||
|
elseif tv == 'table' then
|
||||||
|
self:putTable(v)
|
||||||
|
else
|
||||||
|
self:puts('<', tv, ' ', self:getId(v), '>')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
|
||||||
|
function inspect.inspect(root, options)
|
||||||
|
options = options or {}
|
||||||
|
|
||||||
|
local depth = options.depth or math.huge
|
||||||
|
local newline = options.newline or '\n'
|
||||||
|
local indent = options.indent or ' '
|
||||||
|
local process = options.process
|
||||||
|
|
||||||
|
if process then
|
||||||
|
root = processRecursive(process, root, {}, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
local inspector = setmetatable({
|
||||||
|
depth = depth,
|
||||||
|
level = 0,
|
||||||
|
buffer = {},
|
||||||
|
ids = {},
|
||||||
|
maxIds = {},
|
||||||
|
newline = newline,
|
||||||
|
indent = indent,
|
||||||
|
tableAppearances = countTableAppearances(root)
|
||||||
|
}, Inspector_mt)
|
||||||
|
|
||||||
|
inspector:putValue(root)
|
||||||
|
|
||||||
|
return table.concat(inspector.buffer)
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
|
||||||
|
|
||||||
|
return inspect
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
LOOL
|
|
||||||
|
|
||||||
~ test yep
|
|
||||||
204
test/run.lua
Normal file
204
test/run.lua
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
local lfs = require("lfs")
|
||||||
|
local anselme = require("anselme")
|
||||||
|
local ser = require("test.ser")
|
||||||
|
local inspect = require("test.inspect")
|
||||||
|
|
||||||
|
local function format_text(t, prefix)
|
||||||
|
prefix = prefix or " "
|
||||||
|
local r = ""
|
||||||
|
for _, l in ipairs(t) do
|
||||||
|
r = r .. prefix
|
||||||
|
local tags = ""
|
||||||
|
for k, v in ipairs(l.tags) do
|
||||||
|
tags = tags .. ("[%q]=%q"):format(k, v)
|
||||||
|
end
|
||||||
|
if tags ~= "" then
|
||||||
|
r = r .. ("[%s]%s"):format(tags, l.data)
|
||||||
|
else
|
||||||
|
r = r .. l.data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compare(a, b)
|
||||||
|
if type(a) == "table" and type(b) == "table" then
|
||||||
|
for k, v in pairs(a) do
|
||||||
|
if not compare(v, b[k]) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k, v in pairs(b) do
|
||||||
|
if not compare(v, a[k]) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return a == b
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- parse args
|
||||||
|
local args = {}
|
||||||
|
local i=1
|
||||||
|
while i <= #arg do
|
||||||
|
if arg[i+1] and not arg[i+1]:match("^%-%-") then
|
||||||
|
args[arg[i]:gsub("^%-%-", "")] = arg[i+1]
|
||||||
|
i = i + 2
|
||||||
|
else
|
||||||
|
args[arg[i]:gsub("^%-%-", "")] = true
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- list tests
|
||||||
|
local files = {}
|
||||||
|
for item in lfs.dir("test/tests/") do
|
||||||
|
if item:match("%.ans$") and item:match(args.filter or "") then
|
||||||
|
table.insert(files, "test/tests/"..item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(files)
|
||||||
|
|
||||||
|
-- test script
|
||||||
|
if args.script then
|
||||||
|
local vm = anselme()
|
||||||
|
local state, err = vm:loadfile("test.ans", "test")
|
||||||
|
if state then
|
||||||
|
local istate, e = vm:run("test")
|
||||||
|
if not istate then
|
||||||
|
print("error", e)
|
||||||
|
else
|
||||||
|
repeat
|
||||||
|
local t, d = istate:step()
|
||||||
|
if t == "text" then
|
||||||
|
print(format_text(d))
|
||||||
|
elseif t == "choice" then
|
||||||
|
print(format_text(d, "\n> "))
|
||||||
|
istate:choose(io.read())
|
||||||
|
else
|
||||||
|
print(t, inspect(d))
|
||||||
|
end
|
||||||
|
until t == "return" or t == "error"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print("error", err)
|
||||||
|
end
|
||||||
|
-- run tests
|
||||||
|
else
|
||||||
|
local total, success = #files, 0
|
||||||
|
for _, file in ipairs(files) do
|
||||||
|
local filebase = file:match("^(.*)%.ans$")
|
||||||
|
local namespace = filebase:match("([^/]*)$")
|
||||||
|
math.randomseed(0)
|
||||||
|
local vm = anselme()
|
||||||
|
vm:loadalias {
|
||||||
|
seen = "👁️",
|
||||||
|
checkpoint = "🏁"
|
||||||
|
}
|
||||||
|
vm:loadfunction {
|
||||||
|
-- custom event test
|
||||||
|
["wait"] = {
|
||||||
|
{
|
||||||
|
arity = 1, types = { "number" },
|
||||||
|
value = function(duration)
|
||||||
|
coroutine.yield("wait", duration)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- run another function in parallel
|
||||||
|
["run"] = {
|
||||||
|
{
|
||||||
|
arity = 1, types = { "string" },
|
||||||
|
value = function(str)
|
||||||
|
local istate, e = anselme.running.vm:run(str, anselme.running:current_namespace())
|
||||||
|
if not istate then coroutine.yield("error", e) end
|
||||||
|
local event, data = istate:step()
|
||||||
|
coroutine.yield(event, data)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- manual choice
|
||||||
|
choose = {
|
||||||
|
{
|
||||||
|
arity = 1, types = { "number" },
|
||||||
|
value = function(c)
|
||||||
|
anselme.running:choose(c)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- manual interrupt
|
||||||
|
interrupt = {
|
||||||
|
{
|
||||||
|
arity = 1, types = { "string" },
|
||||||
|
value = function(str)
|
||||||
|
anselme.running:interrupt(str)
|
||||||
|
coroutine.yield("wait", 0)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arity = 0,
|
||||||
|
value = function()
|
||||||
|
anselme.running:interrupt()
|
||||||
|
coroutine.yield("wait", 0)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local state, err = vm:loadfile(file, namespace)
|
||||||
|
|
||||||
|
local result = {}
|
||||||
|
if state then
|
||||||
|
local istate, e = vm:run(namespace)
|
||||||
|
if not istate then
|
||||||
|
table.insert(result, { "error", e })
|
||||||
|
else
|
||||||
|
repeat
|
||||||
|
local t, d = istate:step()
|
||||||
|
table.insert(result, { t, d })
|
||||||
|
until t == "return" or t == "error"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(result, { "error", err })
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.write then
|
||||||
|
local o = assert(io.open(filebase..".lua", "w"))
|
||||||
|
o:write(ser(result))
|
||||||
|
o:write("\n--[[\n")
|
||||||
|
for _, v in ipairs(result) do
|
||||||
|
o:write(inspect(v).."\n")
|
||||||
|
end
|
||||||
|
o:write("]]--")
|
||||||
|
o:close()
|
||||||
|
else
|
||||||
|
local o, e = loadfile(filebase..".lua")
|
||||||
|
if o then
|
||||||
|
local output = o()
|
||||||
|
if not compare(result, output) then
|
||||||
|
if not args.silent then
|
||||||
|
print("> "..namespace)
|
||||||
|
print(inspect(result))
|
||||||
|
print("is not equal to")
|
||||||
|
print(inspect(output))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
success = success + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not args.silent then
|
||||||
|
print("> "..namespace)
|
||||||
|
print(e)
|
||||||
|
print("result was:")
|
||||||
|
print(inspect(result))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if args.write then
|
||||||
|
print("Wrote test results.")
|
||||||
|
else
|
||||||
|
print(("%s/%s tests success."):format(success, total))
|
||||||
|
end
|
||||||
|
end
|
||||||
143
test/ser.lua
Normal file
143
test/ser.lua
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
--[[
|
||||||
|
Copyright (c) 2011,2013 Robin Wellner
|
||||||
|
|
||||||
|
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 pairs, ipairs, tostring, type, concat, dump, floor, format = pairs, ipairs, tostring, type, table.concat, string.dump, math.floor, string.format
|
||||||
|
|
||||||
|
local function getchr(c)
|
||||||
|
return "\\" .. c:byte()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_safe(text)
|
||||||
|
return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr)
|
||||||
|
end
|
||||||
|
|
||||||
|
local oddvals = {[tostring(1/0)] = '1/0', [tostring(-1/0)] = '-1/0', [tostring(-(0/0))] = '-(0/0)', [tostring(0/0)] = '0/0'}
|
||||||
|
local function write(t, memo, rev_memo)
|
||||||
|
local ty = type(t)
|
||||||
|
if ty == 'number' then
|
||||||
|
t = format("%.17g", t)
|
||||||
|
return oddvals[t] or t
|
||||||
|
elseif ty == 'boolean' or ty == 'nil' then
|
||||||
|
return tostring(t)
|
||||||
|
elseif ty == 'string' then
|
||||||
|
return make_safe(t)
|
||||||
|
elseif ty == 'table' or ty == 'function' then
|
||||||
|
if not memo[t] then
|
||||||
|
local index = #rev_memo + 1
|
||||||
|
memo[t] = index
|
||||||
|
rev_memo[index] = t
|
||||||
|
end
|
||||||
|
return '_[' .. memo[t] .. ']'
|
||||||
|
else
|
||||||
|
error("Trying to serialize unsupported type " .. ty)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local kw = {['and'] = true, ['break'] = true, ['do'] = true, ['else'] = true,
|
||||||
|
['elseif'] = true, ['end'] = true, ['false'] = true, ['for'] = true,
|
||||||
|
['function'] = true, ['goto'] = true, ['if'] = true, ['in'] = true,
|
||||||
|
['local'] = true, ['nil'] = true, ['not'] = true, ['or'] = true,
|
||||||
|
['repeat'] = true, ['return'] = true, ['then'] = true, ['true'] = true,
|
||||||
|
['until'] = true, ['while'] = true}
|
||||||
|
local function write_key_value_pair(k, v, memo, rev_memo, name)
|
||||||
|
if type(k) == 'string' and k:match '^[_%a][_%w]*$' and not kw[k] then
|
||||||
|
return (name and name .. '.' or '') .. k ..'=' .. write(v, memo, rev_memo)
|
||||||
|
else
|
||||||
|
return (name or '') .. '[' .. write(k, memo, rev_memo) .. ']=' .. write(v, memo, rev_memo)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fun fact: this function is not perfect
|
||||||
|
-- it has a few false positives sometimes
|
||||||
|
-- but no false negatives, so that's good
|
||||||
|
local function is_cyclic(memo, sub, super)
|
||||||
|
local m = memo[sub]
|
||||||
|
local p = memo[super]
|
||||||
|
return m and p and m < p
|
||||||
|
end
|
||||||
|
|
||||||
|
local function write_table_ex(t, memo, rev_memo, srefs, name)
|
||||||
|
if type(t) == 'function' then
|
||||||
|
return '_[' .. name .. ']=loadstring' .. make_safe(dump(t))
|
||||||
|
end
|
||||||
|
local m = {}
|
||||||
|
local mi = 1
|
||||||
|
for i = 1, #t do -- don't use ipairs here, we need the gaps
|
||||||
|
local v = t[i]
|
||||||
|
if v == t or is_cyclic(memo, v, t) then
|
||||||
|
srefs[#srefs + 1] = {name, i, v}
|
||||||
|
m[mi] = 'nil'
|
||||||
|
mi = mi + 1
|
||||||
|
else
|
||||||
|
m[mi] = write(v, memo, rev_memo)
|
||||||
|
mi = mi + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
if type(k) ~= 'number' or floor(k) ~= k or k < 1 or k > #t then
|
||||||
|
if v == t or k == t or is_cyclic(memo, v, t) or is_cyclic(memo, k, t) then
|
||||||
|
srefs[#srefs + 1] = {name, k, v}
|
||||||
|
else
|
||||||
|
m[mi] = write_key_value_pair(k, v, memo, rev_memo)
|
||||||
|
mi = mi + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return '_[' .. name .. ']={' .. concat(m, ',') .. '}'
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(t)
|
||||||
|
local memo = {[t] = 0}
|
||||||
|
local rev_memo = {[0] = t}
|
||||||
|
local srefs = {}
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
-- phase 1: recursively descend the table structure
|
||||||
|
local n = 0
|
||||||
|
while rev_memo[n] do
|
||||||
|
result[n + 1] = write_table_ex(rev_memo[n], memo, rev_memo, srefs, n)
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- phase 2: reverse order
|
||||||
|
for i = 1, n*.5 do
|
||||||
|
local j = n - i + 1
|
||||||
|
result[i], result[j] = result[j], result[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- phase 3: add all the tricky cyclic stuff
|
||||||
|
for i, v in ipairs(srefs) do
|
||||||
|
n = n + 1
|
||||||
|
result[n] = write_key_value_pair(v[2], v[3], memo, rev_memo, '_[' .. v[1] .. ']')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- phase 4: add something about returning the main table
|
||||||
|
if result[n]:sub(1, 5) == '_[0]=' then
|
||||||
|
result[n] = 'return ' .. result[n]:sub(6)
|
||||||
|
else
|
||||||
|
result[n + 1] = 'return _[0]'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- phase 5: just concatenate everything
|
||||||
|
result = concat(result, '\n')
|
||||||
|
return n > 1 and 'local _={}\n' .. result or result
|
||||||
|
end
|
||||||
11
test/tests/choice block.ans
Normal file
11
test/tests/choice block.ans
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
> ye
|
||||||
|
no
|
||||||
|
> ne
|
||||||
|
ok
|
||||||
|
~ choose(2)
|
||||||
|
|
||||||
|
> ho
|
||||||
|
plop
|
||||||
|
> oh
|
||||||
|
plup
|
||||||
|
~ choose(1)
|
||||||
48
test/tests/choice block.lua
Normal file
48
test/tests/choice block.lua
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
local _={}
|
||||||
|
_[21]={}
|
||||||
|
_[20]={}
|
||||||
|
_[19]={}
|
||||||
|
_[18]={}
|
||||||
|
_[17]={}
|
||||||
|
_[16]={}
|
||||||
|
_[15]={data="plop",tags=_[21]}
|
||||||
|
_[14]={data="oh",tags=_[20]}
|
||||||
|
_[13]={data="ho",tags=_[19]}
|
||||||
|
_[12]={data="ok",tags=_[18]}
|
||||||
|
_[11]={data="ne",tags=_[17]}
|
||||||
|
_[10]={data="ye",tags=_[16]}
|
||||||
|
_[9]={_[15]}
|
||||||
|
_[8]={_[13],_[14]}
|
||||||
|
_[7]={_[12]}
|
||||||
|
_[6]={_[10],_[11]}
|
||||||
|
_[5]={"return"}
|
||||||
|
_[4]={"text",_[9]}
|
||||||
|
_[3]={"choice",_[8]}
|
||||||
|
_[2]={"text",_[7]}
|
||||||
|
_[1]={"choice",_[6]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5]}
|
||||||
|
--[[
|
||||||
|
{ "choice", { {
|
||||||
|
data = "ye",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "ne",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "choice", { {
|
||||||
|
data = "ho",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "oh",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "plop",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
11
test/tests/choice function.ans
Normal file
11
test/tests/choice function.ans
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
$ f
|
||||||
|
> neol
|
||||||
|
nah
|
||||||
|
|
||||||
|
> ho
|
||||||
|
plop
|
||||||
|
~ f
|
||||||
|
> oh
|
||||||
|
ok
|
||||||
|
~ f
|
||||||
|
~ choose(3)
|
||||||
37
test/tests/choice function.lua
Normal file
37
test/tests/choice function.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
local _={}
|
||||||
|
_[15]={}
|
||||||
|
_[14]={}
|
||||||
|
_[13]={}
|
||||||
|
_[12]={}
|
||||||
|
_[11]={}
|
||||||
|
_[10]={data="ok",tags=_[15]}
|
||||||
|
_[9]={data="neol",tags=_[14]}
|
||||||
|
_[8]={data="oh",tags=_[13]}
|
||||||
|
_[7]={data="neol",tags=_[12]}
|
||||||
|
_[6]={data="ho",tags=_[11]}
|
||||||
|
_[5]={_[10]}
|
||||||
|
_[4]={_[6],_[7],_[8],_[9]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[5]}
|
||||||
|
_[1]={"choice",_[4]}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "choice", { {
|
||||||
|
data = "ho",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "neol",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "oh",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "neol",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
5
test/tests/choice simple.ans
Normal file
5
test/tests/choice simple.ans
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
> ye
|
||||||
|
no
|
||||||
|
> ne
|
||||||
|
ok
|
||||||
|
~ choose(2)
|
||||||
27
test/tests/choice simple.lua
Normal file
27
test/tests/choice simple.lua
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
local _={}
|
||||||
|
_[11]={}
|
||||||
|
_[10]={}
|
||||||
|
_[9]={}
|
||||||
|
_[8]={data="ok",tags=_[11]}
|
||||||
|
_[7]={data="ne",tags=_[10]}
|
||||||
|
_[6]={data="ye",tags=_[9]}
|
||||||
|
_[5]={_[8]}
|
||||||
|
_[4]={_[6],_[7]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[5]}
|
||||||
|
_[1]={"choice",_[4]}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "choice", { {
|
||||||
|
data = "ye",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "ne",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
7
test/tests/comment block.ans
Normal file
7
test/tests/comment block.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
(hey couic + 5)
|
||||||
|
other stuff
|
||||||
|
|
||||||
|
CHAZOUM
|
||||||
|
OO
|
||||||
|
|
||||||
|
k
|
||||||
6
test/tests/comment block.lua
Normal file
6
test/tests/comment block.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"return"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
1
test/tests/comment.ans
Normal file
1
test/tests/comment.ans
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
(hey couic + 5)
|
||||||
6
test/tests/comment.lua
Normal file
6
test/tests/comment.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"return"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
20
test/tests/commit.ans
Normal file
20
test/tests/commit.ans
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
$ bar
|
||||||
|
:5 var
|
||||||
|
|
||||||
|
~ var := 2
|
||||||
|
|
||||||
|
before: {var}
|
||||||
|
|
||||||
|
~ run("parallel")
|
||||||
|
|
||||||
|
§ foo
|
||||||
|
checkpoint
|
||||||
|
|
||||||
|
after: {var}
|
||||||
|
|
||||||
|
~ run("parallel")
|
||||||
|
|
||||||
|
$ parallel
|
||||||
|
parallel: {bar.var}
|
||||||
|
|
||||||
|
~ bar
|
||||||
38
test/tests/commit.lua
Normal file
38
test/tests/commit.lua
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
local _={}
|
||||||
|
_[17]={}
|
||||||
|
_[16]={}
|
||||||
|
_[15]={}
|
||||||
|
_[14]={}
|
||||||
|
_[13]={data="parallel: 2",tags=_[17]}
|
||||||
|
_[12]={data="after: 2",tags=_[16]}
|
||||||
|
_[11]={data="parallel: 5",tags=_[15]}
|
||||||
|
_[10]={data="before: 2",tags=_[14]}
|
||||||
|
_[9]={_[13]}
|
||||||
|
_[8]={_[12]}
|
||||||
|
_[7]={_[11]}
|
||||||
|
_[6]={_[10]}
|
||||||
|
_[5]={"return"}
|
||||||
|
_[4]={"text",_[9]}
|
||||||
|
_[3]={"text",_[8]}
|
||||||
|
_[2]={"text",_[7]}
|
||||||
|
_[1]={"text",_[6]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "before: 2",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "parallel: 5",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "after: 2",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "parallel: 2",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
3
test/tests/condition decorator.ans
Normal file
3
test/tests/condition decorator.ans
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ko ~ 0
|
||||||
|
ok ~ 1
|
||||||
|
ok bis ~
|
||||||
19
test/tests/condition decorator.lua
Normal file
19
test/tests/condition decorator.lua
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
local _={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={}
|
||||||
|
_[5]={data="ok bis",tags=_[7]}
|
||||||
|
_[4]={data="ok",tags=_[6]}
|
||||||
|
_[3]={_[4],_[5]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "ok bis",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
6
test/tests/condition else false.ans
Normal file
6
test/tests/condition else false.ans
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
:5 a
|
||||||
|
|
||||||
|
~ a = 2
|
||||||
|
ko
|
||||||
|
~~
|
||||||
|
ok
|
||||||
14
test/tests/condition else false.lua
Normal file
14
test/tests/condition else false.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
6
test/tests/condition else true.ans
Normal file
6
test/tests/condition else true.ans
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
:5 a
|
||||||
|
|
||||||
|
~ a = 5
|
||||||
|
ok
|
||||||
|
~~
|
||||||
|
ko
|
||||||
14
test/tests/condition else true.lua
Normal file
14
test/tests/condition else true.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
8
test/tests/condition elseif false.ans
Normal file
8
test/tests/condition elseif false.ans
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
:5 a
|
||||||
|
|
||||||
|
~ a = 2
|
||||||
|
ko
|
||||||
|
~~ 0
|
||||||
|
ko
|
||||||
|
~~
|
||||||
|
ok
|
||||||
14
test/tests/condition elseif false.lua
Normal file
14
test/tests/condition elseif false.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
8
test/tests/condition elseif true.ans
Normal file
8
test/tests/condition elseif true.ans
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
:5 a
|
||||||
|
|
||||||
|
~ a = 2
|
||||||
|
ko
|
||||||
|
~~ 1
|
||||||
|
ok
|
||||||
|
~~
|
||||||
|
ko
|
||||||
14
test/tests/condition elseif true.lua
Normal file
14
test/tests/condition elseif true.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/condition false.ans
Normal file
4
test/tests/condition false.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
:5 a
|
||||||
|
|
||||||
|
~ a = 2
|
||||||
|
ko
|
||||||
6
test/tests/condition false.lua
Normal file
6
test/tests/condition false.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"return"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/condition true.ans
Normal file
4
test/tests/condition true.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
:5 a
|
||||||
|
|
||||||
|
~ a = 5
|
||||||
|
ok
|
||||||
14
test/tests/condition true.lua
Normal file
14
test/tests/condition true.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
3
test/tests/custom event.ans
Normal file
3
test/tests/custom event.ans
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ah
|
||||||
|
~ wait(5)
|
||||||
|
ho
|
||||||
21
test/tests/custom event.lua
Normal file
21
test/tests/custom event.lua
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
local _={}
|
||||||
|
_[8]={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={data="ho",tags=_[8]}
|
||||||
|
_[5]={data="ah",tags=_[7]}
|
||||||
|
_[4]={_[5],_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[4]}
|
||||||
|
_[1]={"wait",5}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "wait", 5 }
|
||||||
|
{ "text", { {
|
||||||
|
data = "ah",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "ho",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
3
test/tests/define override function.ans
Normal file
3
test/tests/define override function.ans
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
$ a
|
||||||
|
|
||||||
|
:2 a
|
||||||
6
test/tests/define override function.lua
Normal file
6
test/tests/define override function.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"error","trying to define variable define override function.a, but a function with the same name exists; at line 3"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "error", "trying to define variable define override function.a, but a function with the same name exists; at line 3" }
|
||||||
|
]]--
|
||||||
3
test/tests/define override variable.ans
Normal file
3
test/tests/define override variable.ans
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
:2 a
|
||||||
|
|
||||||
|
$ a
|
||||||
6
test/tests/define override variable.lua
Normal file
6
test/tests/define override variable.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"error","trying to define function define override variable.a, but a variable with the same name exists; at line 3"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "error", "trying to define function define override variable.a, but a variable with the same name exists; at line 3" }
|
||||||
|
]]--
|
||||||
5
test/tests/define override.ans
Normal file
5
test/tests/define override.ans
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
:5 a
|
||||||
|
|
||||||
|
:2 a
|
||||||
|
|
||||||
|
a: {a}
|
||||||
14
test/tests/define override.lua
Normal file
14
test/tests/define override.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="a: 5",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a: 5",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
1
test/tests/define.ans
Normal file
1
test/tests/define.ans
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
:5 a
|
||||||
6
test/tests/define.lua
Normal file
6
test/tests/define.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"return"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
5
test/tests/function arg vararg.ans
Normal file
5
test/tests/function arg vararg.ans
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
$ f(a, l...)
|
||||||
|
{a}
|
||||||
|
{l}
|
||||||
|
|
||||||
|
~ f("ok", "o", "k")
|
||||||
19
test/tests/function arg vararg.lua
Normal file
19
test/tests/function arg vararg.lua
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
local _={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={}
|
||||||
|
_[5]={data="[o, k]",tags=_[7]}
|
||||||
|
_[4]={data="ok",tags=_[6]}
|
||||||
|
_[3]={_[4],_[5]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "[o, k]",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function arg.ans
Normal file
4
test/tests/function arg.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ f(a)
|
||||||
|
{a}
|
||||||
|
|
||||||
|
~ f("ok")
|
||||||
14
test/tests/function arg.lua
Normal file
14
test/tests/function arg.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function args arity check fail.ans
Normal file
4
test/tests/function args arity check fail.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ f(a, b)
|
||||||
|
{a}{b}
|
||||||
|
|
||||||
|
~ f("ok")
|
||||||
6
test/tests/function args arity check fail.lua
Normal file
6
test/tests/function args arity check fail.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"error","function \"function args arity check fail.f\" expected 2 arguments but received 1; at line 4"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "error", 'function "function args arity check fail.f" expected 2 arguments but received 1; at line 4' }
|
||||||
|
]]--
|
||||||
5
test/tests/function args vararg empty.ans
Normal file
5
test/tests/function args vararg empty.ans
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
$ f(a, b, l...)
|
||||||
|
{a}{b}
|
||||||
|
{l}
|
||||||
|
|
||||||
|
~ f("o","k")
|
||||||
19
test/tests/function args vararg empty.lua
Normal file
19
test/tests/function args vararg empty.lua
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
local _={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={}
|
||||||
|
_[5]={data="[]",tags=_[7]}
|
||||||
|
_[4]={data="ok",tags=_[6]}
|
||||||
|
_[3]={_[4],_[5]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "[]",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
5
test/tests/function args vararg.ans
Normal file
5
test/tests/function args vararg.ans
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
$ f(a, b, l...)
|
||||||
|
{a}{b}
|
||||||
|
{l}
|
||||||
|
|
||||||
|
~ f("o", "k", "o", "k")
|
||||||
19
test/tests/function args vararg.lua
Normal file
19
test/tests/function args vararg.lua
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
local _={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={}
|
||||||
|
_[5]={data="[o, k]",tags=_[7]}
|
||||||
|
_[4]={data="ok",tags=_[6]}
|
||||||
|
_[3]={_[4],_[5]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "[o, k]",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function args.ans
Normal file
4
test/tests/function args.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ f(a, b)
|
||||||
|
{a}{b}
|
||||||
|
|
||||||
|
~ f("o", "k")
|
||||||
14
test/tests/function args.lua
Normal file
14
test/tests/function args.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
5
test/tests/function arity conflict.ans
Normal file
5
test/tests/function arity conflict.ans
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
$ f(a, b)
|
||||||
|
|
||||||
|
$ f(x)
|
||||||
|
|
||||||
|
$ f(u, v)
|
||||||
6
test/tests/function arity conflict.lua
Normal file
6
test/tests/function arity conflict.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"error","trying to define function function arity conflict.f with arity [2;2], but another function with the arity exist; at line 5"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "error", "trying to define function function arity conflict.f with arity [2;2], but another function with the arity exist; at line 5" }
|
||||||
|
]]--
|
||||||
14
test/tests/function cycle.ans
Normal file
14
test/tests/function cycle.ans
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
$ f
|
||||||
|
$ a
|
||||||
|
a
|
||||||
|
$ b
|
||||||
|
b
|
||||||
|
$ c
|
||||||
|
c
|
||||||
|
~ cycle("a","b","c")
|
||||||
|
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
46
test/tests/function cycle.lua
Normal file
46
test/tests/function cycle.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
local _={}
|
||||||
|
_[21]={}
|
||||||
|
_[20]={}
|
||||||
|
_[19]={}
|
||||||
|
_[18]={}
|
||||||
|
_[17]={}
|
||||||
|
_[16]={data="b",tags=_[21]}
|
||||||
|
_[15]={data="a",tags=_[20]}
|
||||||
|
_[14]={data="c",tags=_[19]}
|
||||||
|
_[13]={data="b",tags=_[18]}
|
||||||
|
_[12]={data="a",tags=_[17]}
|
||||||
|
_[11]={_[16]}
|
||||||
|
_[10]={_[15]}
|
||||||
|
_[9]={_[14]}
|
||||||
|
_[8]={_[13]}
|
||||||
|
_[7]={_[12]}
|
||||||
|
_[6]={"return"}
|
||||||
|
_[5]={"text",_[11]}
|
||||||
|
_[4]={"text",_[10]}
|
||||||
|
_[3]={"text",_[9]}
|
||||||
|
_[2]={"text",_[8]}
|
||||||
|
_[1]={"text",_[7]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5],_[6]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "c",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
14
test/tests/function next.ans
Normal file
14
test/tests/function next.ans
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
$ f
|
||||||
|
$ a
|
||||||
|
a
|
||||||
|
$ b
|
||||||
|
b
|
||||||
|
$ c
|
||||||
|
c
|
||||||
|
~ next("a","b","c")
|
||||||
|
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
46
test/tests/function next.lua
Normal file
46
test/tests/function next.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
local _={}
|
||||||
|
_[21]={}
|
||||||
|
_[20]={}
|
||||||
|
_[19]={}
|
||||||
|
_[18]={}
|
||||||
|
_[17]={}
|
||||||
|
_[16]={data="c",tags=_[21]}
|
||||||
|
_[15]={data="c",tags=_[20]}
|
||||||
|
_[14]={data="c",tags=_[19]}
|
||||||
|
_[13]={data="b",tags=_[18]}
|
||||||
|
_[12]={data="a",tags=_[17]}
|
||||||
|
_[11]={_[16]}
|
||||||
|
_[10]={_[15]}
|
||||||
|
_[9]={_[14]}
|
||||||
|
_[8]={_[13]}
|
||||||
|
_[7]={_[12]}
|
||||||
|
_[6]={"return"}
|
||||||
|
_[5]={"text",_[11]}
|
||||||
|
_[4]={"text",_[10]}
|
||||||
|
_[3]={"text",_[9]}
|
||||||
|
_[2]={"text",_[8]}
|
||||||
|
_[1]={"text",_[7]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5],_[6]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "c",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "c",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "c",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
14
test/tests/function random.ans
Normal file
14
test/tests/function random.ans
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
$ f
|
||||||
|
$ a
|
||||||
|
a
|
||||||
|
$ b
|
||||||
|
b
|
||||||
|
$ c
|
||||||
|
c
|
||||||
|
~ random("a","b","c")
|
||||||
|
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
|
~ f
|
||||||
46
test/tests/function random.lua
Normal file
46
test/tests/function random.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
local _={}
|
||||||
|
_[21]={}
|
||||||
|
_[20]={}
|
||||||
|
_[19]={}
|
||||||
|
_[18]={}
|
||||||
|
_[17]={}
|
||||||
|
_[16]={data="a",tags=_[21]}
|
||||||
|
_[15]={data="c",tags=_[20]}
|
||||||
|
_[14]={data="c",tags=_[19]}
|
||||||
|
_[13]={data="c",tags=_[18]}
|
||||||
|
_[12]={data="b",tags=_[17]}
|
||||||
|
_[11]={_[16]}
|
||||||
|
_[10]={_[15]}
|
||||||
|
_[9]={_[14]}
|
||||||
|
_[8]={_[13]}
|
||||||
|
_[7]={_[12]}
|
||||||
|
_[6]={"return"}
|
||||||
|
_[5]={"text",_[11]}
|
||||||
|
_[4]={"text",_[10]}
|
||||||
|
_[3]={"text",_[9]}
|
||||||
|
_[2]={"text",_[8]}
|
||||||
|
_[1]={"text",_[7]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5],_[6]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "c",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "c",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "c",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
7
test/tests/function return nested.ans
Normal file
7
test/tests/function return nested.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
$ hey
|
||||||
|
§ foo
|
||||||
|
@2
|
||||||
|
@5
|
||||||
|
|
||||||
|
{hey}
|
||||||
|
{hey.foo}
|
||||||
19
test/tests/function return nested.lua
Normal file
19
test/tests/function return nested.lua
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
local _={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={}
|
||||||
|
_[5]={data="2",tags=_[7]}
|
||||||
|
_[4]={data="5",tags=_[6]}
|
||||||
|
_[3]={_[4],_[5]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "5",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "2",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function return.ans
Normal file
4
test/tests/function return.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ hey
|
||||||
|
@5
|
||||||
|
|
||||||
|
{hey}
|
||||||
14
test/tests/function return.lua
Normal file
14
test/tests/function return.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="5",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "5",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function scope wrong.ans
Normal file
4
test/tests/function scope wrong.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ a
|
||||||
|
:5 b
|
||||||
|
|
||||||
|
a: {b}
|
||||||
6
test/tests/function scope wrong.lua
Normal file
6
test/tests/function scope wrong.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"error","unknown identifier \"b\"; at line 4"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "error", 'unknown identifier "b"; at line 4' }
|
||||||
|
]]--
|
||||||
4
test/tests/function scope.ans
Normal file
4
test/tests/function scope.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ a
|
||||||
|
:5 b
|
||||||
|
|
||||||
|
a: {a.b}
|
||||||
14
test/tests/function scope.lua
Normal file
14
test/tests/function scope.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="a: 5",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a: 5",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function ufcs arg.ans
Normal file
4
test/tests/function ufcs arg.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ f(a)
|
||||||
|
{a}
|
||||||
|
|
||||||
|
~ "ok".f()
|
||||||
14
test/tests/function ufcs arg.lua
Normal file
14
test/tests/function ufcs arg.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function ufcs args.ans
Normal file
4
test/tests/function ufcs args.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ f(a, b)
|
||||||
|
{a}{b}
|
||||||
|
|
||||||
|
~ "o".f("k")
|
||||||
14
test/tests/function ufcs args.lua
Normal file
14
test/tests/function ufcs args.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function vararg empty.ans
Normal file
4
test/tests/function vararg empty.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ f(l...)
|
||||||
|
{l}
|
||||||
|
|
||||||
|
~ f()
|
||||||
14
test/tests/function vararg empty.lua
Normal file
14
test/tests/function vararg empty.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="[]",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "[]",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function vararg.ans
Normal file
4
test/tests/function vararg.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ f(l...)
|
||||||
|
{l}
|
||||||
|
|
||||||
|
~ f("o", "k")
|
||||||
14
test/tests/function vararg.lua
Normal file
14
test/tests/function vararg.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="[o, k]",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "[o, k]",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
4
test/tests/function.ans
Normal file
4
test/tests/function.ans
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
$ f
|
||||||
|
ok
|
||||||
|
|
||||||
|
~ f
|
||||||
14
test/tests/function.lua
Normal file
14
test/tests/function.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="ok",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
19
test/tests/interrupt callback nested paragraph.ans
Normal file
19
test/tests/interrupt callback nested paragraph.ans
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
$ oh
|
||||||
|
§ leave
|
||||||
|
in interrupt: {bar.var}
|
||||||
|
no
|
||||||
|
$ bar
|
||||||
|
:5 var
|
||||||
|
|
||||||
|
~ var := 2
|
||||||
|
|
||||||
|
before: {var}
|
||||||
|
|
||||||
|
~ interrupt("leave")
|
||||||
|
|
||||||
|
§ foo
|
||||||
|
checkpoint
|
||||||
|
|
||||||
|
after: {var}
|
||||||
|
|
||||||
|
~ oh.bar
|
||||||
29
test/tests/interrupt callback nested paragraph.lua
Normal file
29
test/tests/interrupt callback nested paragraph.lua
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
local _={}
|
||||||
|
_[12]={}
|
||||||
|
_[11]={}
|
||||||
|
_[10]={}
|
||||||
|
_[9]={data="no",tags=_[12]}
|
||||||
|
_[8]={data="in interrupt: 5",tags=_[11]}
|
||||||
|
_[7]={data="before: 2",tags=_[10]}
|
||||||
|
_[6]={_[8],_[9]}
|
||||||
|
_[5]={_[7]}
|
||||||
|
_[4]={"return"}
|
||||||
|
_[3]={"text",_[6]}
|
||||||
|
_[2]={"wait",0}
|
||||||
|
_[1]={"text",_[5]}
|
||||||
|
return {_[1],_[2],_[3],_[4]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "before: 2",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "wait", 0 }
|
||||||
|
{ "text", { {
|
||||||
|
data = "in interrupt: 5",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "no",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
20
test/tests/interrupt callback nested.ans
Normal file
20
test/tests/interrupt callback nested.ans
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
$ leave
|
||||||
|
in interrupt: {oh.bar.var}
|
||||||
|
|
||||||
|
$ oh
|
||||||
|
no
|
||||||
|
$ bar
|
||||||
|
:5 var
|
||||||
|
|
||||||
|
~ var := 2
|
||||||
|
|
||||||
|
before: {var}
|
||||||
|
|
||||||
|
~ interrupt("leave")
|
||||||
|
|
||||||
|
§ foo
|
||||||
|
checkpoint
|
||||||
|
|
||||||
|
after: {var}
|
||||||
|
|
||||||
|
~ oh.bar
|
||||||
24
test/tests/interrupt callback nested.lua
Normal file
24
test/tests/interrupt callback nested.lua
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
local _={}
|
||||||
|
_[10]={}
|
||||||
|
_[9]={}
|
||||||
|
_[8]={data="in interrupt: 5",tags=_[10]}
|
||||||
|
_[7]={data="before: 2",tags=_[9]}
|
||||||
|
_[6]={_[8]}
|
||||||
|
_[5]={_[7]}
|
||||||
|
_[4]={"return"}
|
||||||
|
_[3]={"text",_[6]}
|
||||||
|
_[2]={"wait",0}
|
||||||
|
_[1]={"text",_[5]}
|
||||||
|
return {_[1],_[2],_[3],_[4]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "before: 2",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "wait", 0 }
|
||||||
|
{ "text", { {
|
||||||
|
data = "in interrupt: 5",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
18
test/tests/interrupt callback.ans
Normal file
18
test/tests/interrupt callback.ans
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
$ bar
|
||||||
|
:5 var
|
||||||
|
|
||||||
|
~ var := 2
|
||||||
|
|
||||||
|
$ leave
|
||||||
|
in interrupt: {var}
|
||||||
|
|
||||||
|
before: {var}
|
||||||
|
|
||||||
|
~ interrupt("leave")
|
||||||
|
|
||||||
|
§ foo
|
||||||
|
checkpoint
|
||||||
|
|
||||||
|
after: {var}
|
||||||
|
|
||||||
|
~ bar
|
||||||
24
test/tests/interrupt callback.lua
Normal file
24
test/tests/interrupt callback.lua
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
local _={}
|
||||||
|
_[10]={}
|
||||||
|
_[9]={}
|
||||||
|
_[8]={data="in interrupt: 5",tags=_[10]}
|
||||||
|
_[7]={data="before: 2",tags=_[9]}
|
||||||
|
_[6]={_[8]}
|
||||||
|
_[5]={_[7]}
|
||||||
|
_[4]={"return"}
|
||||||
|
_[3]={"text",_[6]}
|
||||||
|
_[2]={"wait",0}
|
||||||
|
_[1]={"text",_[5]}
|
||||||
|
return {_[1],_[2],_[3],_[4]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "before: 2",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "wait", 0 }
|
||||||
|
{ "text", { {
|
||||||
|
data = "in interrupt: 5",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue