mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Changed a few things
* Integrated # and ~ decorators into the expression system. Created associated operators. * # and ~ decorators only affect their current line. That's more useful... * Fix our priority system to evaluate left-to-right instead of right-to-left (if there was a reason why I did it this way initially, I don't remember it so ¯\_(ツ)_/¯) * a lotta internal changes Various other small adjustments, see the diff of REFERENCE.md for details.
This commit is contained in:
parent
14d348bad9
commit
f2e74c94c9
31 changed files with 894 additions and 343 deletions
87
REFERENCE.md
87
REFERENCE.md
|
|
@ -156,7 +156,7 @@ There's different types of lines, depending on their first character(s) (after i
|
|||
This is.
|
||||
```
|
||||
|
||||
* `>`: write a choice into the [event buffer](#event-buffer). Followed by arbitrary text. Support [text interpolation](#text-interpolation); if a text event is created during the text interpolation, it is added to the choice text content instead of the global event buffer.
|
||||
* `>`: write a choice into the [event buffer](#event-buffer). Followed by arbitrary text. Support [text interpolation](#text-interpolation); if a text event is created during the text interpolation, it is added to the choice text content instead of the global event buffer. Support [escape codes](#escape-codes). Empty choices are discarded.
|
||||
|
||||
```
|
||||
$ f
|
||||
|
|
@ -168,6 +168,18 @@ $ f
|
|||
> {f}
|
||||
```
|
||||
|
||||
If an unescaped `~` or `#` appears in the line, the associated operator is applied to the line (see [operators](#operators)), using the previous text as the left argument and everything that follows as the right argument expression.
|
||||
|
||||
```
|
||||
(Conditionnaly executing a line)
|
||||
$ fn
|
||||
> show this choice only once ~ 👁️
|
||||
|
||||
(Tagging a single line)
|
||||
> tagged # 42
|
||||
not tagged
|
||||
```
|
||||
|
||||
* `$`: function line. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases), and eventually a parameter list. Define a function using its children as function body. Also define a new namespace for its children (using the function name if it has no arguments, or a unique name otherwise).
|
||||
|
||||
The function body is not executed when the line is reached; it must be explicitely called in an expression. See [expressions](#function-calls) to see the different ways of calling a function.
|
||||
|
|
@ -223,7 +235,7 @@ $ f(x::string)
|
|||
~ f("hello")
|
||||
```
|
||||
|
||||
Every operator, except assignement operators, `|`, `&` and `,` can also be use as a function name in order to overload the operator:
|
||||
Every operator, except assignement operators, `|`, `&`, `,`, `~` and `#` can also be use as a function name in order to overload the operator:
|
||||
|
||||
```
|
||||
$ /(a::string, b::string)
|
||||
|
|
@ -323,7 +335,7 @@ $ f
|
|||
|
||||
* empty line: flush the event buffer, i.e., if there are any pending lines of text or choices, send them to your game. See [Event buffer](#event-buffer). This line always keep the same identation as the last non-empty line, so you don't need to put invisible whitespace on an empty-looking line. Is also automatically added at the end of a file.
|
||||
|
||||
* regular text: write some text into the [event buffer](#event-buffer). Support [text interpolation](#text-interpolation).
|
||||
* regular text: write some text into the [event buffer](#event-buffer). Support [text interpolation](#text-interpolation). Support [escape codes](#escape-codes).
|
||||
|
||||
```
|
||||
Hello,
|
||||
|
|
@ -332,37 +344,20 @@ this is some text.
|
|||
And this is more text, in a different event.
|
||||
```
|
||||
|
||||
### Line decorators
|
||||
|
||||
Every line can also be followed with decorators, which are appended at the end of the line and affect its behaviour. Decorators are just syntaxic sugar to make some common operations simpler to write.
|
||||
|
||||
* `~`: condition decorator. Same as an condition line, behaving as if this line was it sole child. Typically used to conditionally execute line.
|
||||
If an unescaped `~` or `#` appears in the line, the associated operator is applied to the line (see [operators](#operators)), using the previous text as the left argument and everything that follows as the right argument expression.
|
||||
|
||||
```
|
||||
(Conditionnaly executing a line)
|
||||
$ fn
|
||||
run this line only once ~ 👁️
|
||||
```
|
||||
|
||||
is equivalent to:
|
||||
|
||||
```
|
||||
$ fn
|
||||
~ 👁️
|
||||
run this line only once
|
||||
```
|
||||
|
||||
* `#`: tag decorator. Same as a tag line, behaving as if this line was it sole child.
|
||||
|
||||
```
|
||||
(Tagging a single line)
|
||||
tagged # 42
|
||||
```
|
||||
|
||||
is equivalent to:
|
||||
### Line decorators
|
||||
|
||||
```
|
||||
# 42
|
||||
tagged
|
||||
```
|
||||
Every line can also be followed with decorators, which are appended at the end of the line and affect its behaviour. Decorators are just syntaxic sugar to make some common operations simpler to write.
|
||||
|
||||
* `$`: function decorator. Same as a function line, behaving as if this line was it sole child, but also run the function.
|
||||
|
||||
|
|
@ -428,6 +423,17 @@ Hello {f}
|
|||
:b = "{f}"
|
||||
```
|
||||
|
||||
Text interpolation in text and choices lines also support subtexts: this will process text in squares brackets `[]` in the same way as a regular text line.
|
||||
|
||||
```
|
||||
Hello [world].
|
||||
|
||||
(Typically used to tag part of a line in a compact manner)
|
||||
Hello [world#5]
|
||||
|
||||
> Hello [world#5]
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
Anselme need to give back control to the game at some point. This is done through events: the interpreter regularly yield its coroutine and returns a bunch of data to your game. This is the "event", it is what we call whatever Anselme sends back to your game.
|
||||
|
|
@ -483,14 +489,14 @@ By default, some processing is done on the event buffer before sending it to you
|
|||
* strip trailing spaces: will remove any space caracters at the end of the text (for text event), or at the end of each choice (for choice event).
|
||||
|
||||
```
|
||||
(There is a space between the text and the tag decorator that would be included in the text event otherwise.)
|
||||
(There is a space between the text and the tag expression that would be included in the text event otherwise.)
|
||||
Some text # tag
|
||||
```
|
||||
|
||||
* strip duplicate spaces: will remove any duplicated space caracters between each element that constitute the text (for text event), or for each choice (for choice event).
|
||||
|
||||
```
|
||||
(There is a space between the text and the tag decorator; but there is a space as well after the text interpolation in the last line. The two spaces are converted into a single space (the space will belong to the first text element, i.e. the "text " element).)
|
||||
(There is a space between the text and the tag expression; but there is a space as well after the text interpolation in the last line. The two spaces are converted into a single space (the space will belong to the first text element, i.e. the "text " element).)
|
||||
$ f
|
||||
text # tag
|
||||
|
||||
|
|
@ -577,7 +583,7 @@ Default types are:
|
|||
|
||||
* `number`: a number (double). Can be defined using the forms `42`, `.42`, `42.42`.
|
||||
|
||||
* `string`: a string. Can be defined between double quotes `"string"`. Support [text interpolation](#text-interpolation). Support the escape codes `\\` for `\`, `\"` for `"`, `\n` for a newline and `\t` for a tabulation.
|
||||
* `string`: a string. Can be defined between double quotes `"string"`. Support [text interpolation](#text-interpolation). Support [escape codes](#escape-codes).
|
||||
|
||||
* `list`: a list of values. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'.
|
||||
|
||||
|
|
@ -609,6 +615,19 @@ How conservions are handled from Lua to Anselme:
|
|||
|
||||
* `boolean` -> `number`, 0 for false, 1 for true.
|
||||
|
||||
#### Escapes codes
|
||||
|
||||
These can be used to represent some caracters in string and other text elements that would otherwise be difficult to express due to conflicts with Anselme syntax.
|
||||
|
||||
* `\{` for `{`
|
||||
* `\~` for `~`
|
||||
* `\#` for `#`
|
||||
* `\$` for `$`
|
||||
* `\\` for `\`
|
||||
* `\"` for `"`
|
||||
* `\n` for a newline
|
||||
* `\t` for a tabulation
|
||||
|
||||
#### Truethness
|
||||
|
||||
Only `0` and `nil` are false. Everything else is considered true.
|
||||
|
|
@ -696,7 +715,7 @@ $ f(a, b, c)
|
|||
{f(1,2,3)} = {f(c=3,b=2,a=1)} = {f(1,2,c=3)}
|
||||
```
|
||||
|
||||
Anselme actually treat argument list are regular lists; named arguments are actually pairs.
|
||||
Anselme actually treat argument list are regular lists; named arguments are actually pairs. Arguments are evaluated left-to-right.
|
||||
|
||||
This means that pairs can't be passed directly as arguments to a function (as they will be considered named arguments). If you want to use pairs, always wrap them in a list.
|
||||
|
||||
|
|
@ -792,7 +811,7 @@ Please also be aware that when resuming from a checkpoint, Anselme will try to r
|
|||
|
||||
* if the checkpoint is in a condition block, it will assume the condition was true (but will not re-evaluate it)
|
||||
* if the checkpoint is in a choice block, it will assume this choice was selected (but will not re-evaluate any of the choices from the same choice group)
|
||||
* will try to re-add every tag from parent lines; this require Anselme to re-evaluate every tag line and decorator that's a parent of the checkpoint in the function. Be careful if your tag expressions have side-effects.
|
||||
* will try to re-add every tag from parent lines; this require Anselme to re-evaluate every tag lines that are a parent of the checkpoint in the function. Be careful if your tag expressions have side-effects.
|
||||
|
||||
##### Operator priority
|
||||
|
||||
|
|
@ -802,7 +821,7 @@ From lowest to highest priority:
|
|||
;
|
||||
:= += -= //= /= *= %= ^=
|
||||
,
|
||||
| &
|
||||
| & ~ #
|
||||
!= == >= <= < >
|
||||
+ -
|
||||
* // / %
|
||||
|
|
@ -812,6 +831,8 @@ unary -, unary !
|
|||
.
|
||||
```
|
||||
|
||||
A series of operators with the same priority are evaluated left-to-right.
|
||||
|
||||
#### Operators
|
||||
|
||||
Built-in operators:
|
||||
|
|
@ -868,6 +889,10 @@ This only works on strings:
|
|||
|
||||
`a :: b`: evaluate a and b, returns a new typed value with a as value and b as type.
|
||||
|
||||
`a ~ b`: evaluates b, if true evaluates and returns a, otherwise returns nil (lazy).
|
||||
|
||||
`a # b`: evaluates b, then evaluates a whith b added to the active tags.
|
||||
|
||||
`a(b)`: evaluate b (number), returns the value with this index in a (list). Use 1-based indexing. If b is a string, will search the first pair in the list with this string as its name. Operator is named `()`.
|
||||
|
||||
`{}(v)`: function called when formatting a value in a text interpolation for printing
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ local anselme = {
|
|||
-- api is incremented a each update which may break Lua API compatibility
|
||||
versions = {
|
||||
save = 1,
|
||||
language = 17,
|
||||
language = 18,
|
||||
api = 2
|
||||
},
|
||||
-- version is incremented at each update
|
||||
version = 18,
|
||||
version = 19,
|
||||
--- currently running interpreter
|
||||
running = nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,32 @@
|
|||
local atypes, ltypes
|
||||
local eval
|
||||
local eval, run_block
|
||||
|
||||
local function post_process_text(state, text)
|
||||
-- remove trailing spaces
|
||||
if state.feature_flags["strip trailing spaces"] then
|
||||
local final = text[#text]
|
||||
if final then
|
||||
final.text = final.text:match("^(.-) *$")
|
||||
if final.text == "" then
|
||||
table.remove(text)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- remove duplicate spaces
|
||||
if state.feature_flags["strip duplicate spaces"] then
|
||||
for i=1, #text-1 do
|
||||
local a, b = text[i], text[i+1]
|
||||
local na = #a.text:match(" *$")
|
||||
local nb = #b.text:match("^ *")
|
||||
if na > 0 and nb > 0 then -- remove duplicated spaces from second element first
|
||||
b.text = b.text:match("^ *(.-)$")
|
||||
end
|
||||
if na > 1 then
|
||||
a.text = a.text:match("^(.- ) *$")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local common
|
||||
common = {
|
||||
|
|
@ -150,12 +177,190 @@ common = {
|
|||
else
|
||||
return var.type
|
||||
end
|
||||
end
|
||||
end,
|
||||
--- tag management
|
||||
tags = {
|
||||
--- push new tags on top of the stack, from Anselme values
|
||||
push = function(self, state, val)
|
||||
local new = {}
|
||||
-- copy
|
||||
local last = self:current(state)
|
||||
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(common.to_lua(val)) do new[k] = v end
|
||||
-- add
|
||||
table.insert(state.interpreter.tags, new)
|
||||
return self:len(state)
|
||||
end,
|
||||
--- same but do not merge with last stack item
|
||||
push_lua_no_merge = function(self, state, val)
|
||||
table.insert(state.interpreter.tags, val)
|
||||
return self:len(state)
|
||||
end,
|
||||
-- pop tag table on top of the stack
|
||||
pop = function(self, state)
|
||||
table.remove(state.interpreter.tags)
|
||||
end,
|
||||
--- return current lua tags table
|
||||
current = function(self, state)
|
||||
return state.interpreter.tags[#state.interpreter.tags] or {}
|
||||
end,
|
||||
--- returns length of tags stack
|
||||
len = function(self, state)
|
||||
return #state.interpreter.tags
|
||||
end,
|
||||
--- pop item until we reached desired stack length
|
||||
-- try to prefer this to pop if possible, so in case we mess up the stack somehow it will restore the stack to a good state
|
||||
-- (we may allow tag push/pop from the user side at some point TODO)
|
||||
trim = function(self, state, len)
|
||||
while #state.interpreter.tags > len do
|
||||
self:pop(state)
|
||||
end
|
||||
end
|
||||
},
|
||||
--- event buffer management
|
||||
-- i.e. only for text and choice events
|
||||
events = {
|
||||
--- add a new element to the event buffer
|
||||
-- will flush if needed
|
||||
-- returns true in case of success
|
||||
-- returns nil, err in case of error
|
||||
append = function(self, state, type, data)
|
||||
if state.interpreter.event_capture_stack[type] then
|
||||
local r, e = state.interpreter.event_capture_stack[type][#state.interpreter.event_capture_stack[type]](data)
|
||||
if not r then return r, e end
|
||||
else
|
||||
local r, e = self:make_space_for(state, type)
|
||||
if not r then return r, e 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)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
--- add a new item in the last element (a list of elements) of the event buffer
|
||||
-- will flush if needed
|
||||
-- will use default or a new list if buffer is empty
|
||||
-- returns true in case of success
|
||||
-- returns nil, err in case of error
|
||||
append_in_last = function(self, state, type, data, default)
|
||||
local r, e = self:make_space_for(state, type)
|
||||
if not r then return r, e end
|
||||
|
||||
if not state.interpreter.event_buffer then
|
||||
r, e = self:append(state, type, default or {})
|
||||
if not r then return r, e end
|
||||
end
|
||||
|
||||
table.insert(state.interpreter.event_buffer[#state.interpreter.event_buffer], data)
|
||||
|
||||
return true
|
||||
end,
|
||||
|
||||
--- start capturing events of a certain type
|
||||
-- when an event of the type is appended, fn will be called with this event data
|
||||
-- and the event will not be added to the event buffer
|
||||
-- fn returns nil, err in case of error
|
||||
push_capture = function(self, state, type, fn)
|
||||
if not state.interpreter.event_capture_stack[type] then
|
||||
state.interpreter.event_capture_stack[type] = {}
|
||||
end
|
||||
table.insert(state.interpreter.event_capture_stack[type], fn)
|
||||
end,
|
||||
--- stop capturing events of a certain type.
|
||||
-- must be called after a push_capture
|
||||
-- this is handled by a stack so nested capturing is allowed.
|
||||
pop_capture = function(self, state, type)
|
||||
table.remove(state.interpreter.event_capture_stack[type])
|
||||
if #state.interpreter.event_capture_stack[type] == 0 then
|
||||
state.interpreter.event_capture_stack[type] = nil
|
||||
end
|
||||
end,
|
||||
|
||||
-- flush event buffer if it's neccessary to push an event of the given type
|
||||
-- returns true in case of success
|
||||
-- returns nil, err in case of error
|
||||
make_space_for = function(self, state, type)
|
||||
if state.interpreter.event_buffer and state.interpreter.event_type ~= type and not state.interpreter.event_capture_stack[type] then
|
||||
return self:flush(state)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
--- flush events and send them to the game if possible
|
||||
-- returns true in case of success
|
||||
-- returns nil, err in case of error
|
||||
flush = function(self, state)
|
||||
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
|
||||
state.interpreter.skip_choices_until_flush = nil
|
||||
-- choice processing
|
||||
local choices
|
||||
if type == "choice" then
|
||||
choices = {}
|
||||
-- discard empty choices
|
||||
for i=#buffer, 1, -1 do
|
||||
if #buffer[i] == 0 then
|
||||
table.remove(buffer, i)
|
||||
end
|
||||
end
|
||||
-- extract some needed state data for each choice block
|
||||
for _, c in ipairs(buffer) do
|
||||
table.insert(choices, c._state)
|
||||
c._state = nil
|
||||
end
|
||||
-- nervermind
|
||||
if #choices == 0 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
-- text & choice text content post processing
|
||||
if type == "text" then
|
||||
post_process_text(state, buffer)
|
||||
end
|
||||
if type == "choice" then
|
||||
for _, c in ipairs(buffer) do
|
||||
post_process_text(state, c)
|
||||
end
|
||||
end
|
||||
-- yield event
|
||||
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 > #choices then
|
||||
return nil, "invalid choice"
|
||||
else
|
||||
local choice = choices[sel]
|
||||
-- execute in expected tag & event capture state
|
||||
local capture_state = state.interpreter.event_capture_stack
|
||||
state.interpreter.event_capture_stack = {}
|
||||
local i = common.tags:push_lua_no_merge(state, choice.tags)
|
||||
local _, e = run_block(state, choice.block)
|
||||
common.tags:trim(state, i-1)
|
||||
state.interpreter.event_capture_stack = capture_state
|
||||
if e then return nil, e end
|
||||
-- we discard return value from choice block as the execution is delayed until an event flush
|
||||
-- and we don't want to stop the execution of another function unexpectedly
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
package.loaded[...] = common
|
||||
local types = require((...):gsub("interpreter%.common$", "stdlib.types"))
|
||||
atypes, ltypes = types.anselme, types.lua
|
||||
eval = require((...):gsub("common$", "expression"))
|
||||
run_block = require((...):gsub("common$", "interpreter")).run_block
|
||||
|
||||
return common
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
local expression
|
||||
local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable
|
||||
local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list
|
||||
|
||||
local run
|
||||
|
||||
|
|
@ -54,21 +54,29 @@ local function eval(state, exp)
|
|||
end
|
||||
-- list
|
||||
elseif exp.type == "list" then
|
||||
local flat = flatten_list(exp)
|
||||
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
|
||||
for _, ast in ipairs(flat) do
|
||||
local v, e = eval(state, ast)
|
||||
if not v then return v, e end
|
||||
table.insert(l, v)
|
||||
end
|
||||
local right, righte = eval(state, ast)
|
||||
if not right then return right, righte end
|
||||
table.insert(l, right)
|
||||
return {
|
||||
type = "list",
|
||||
value = l
|
||||
}
|
||||
-- text: only triggered from choice/text lines
|
||||
elseif exp.type == "text" then
|
||||
local currentTags = tags:current(state)
|
||||
local v, e = eval_text_callback(state, exp.text, function(text)
|
||||
local v2, e2 = events:append(state, "text", { text = text, tags = currentTags })
|
||||
if not v2 then return v2, e2 end
|
||||
end)
|
||||
if not v then return v, e end
|
||||
return {
|
||||
type = "nil",
|
||||
value = nil
|
||||
}
|
||||
-- assignment
|
||||
elseif exp.type == ":=" then
|
||||
if exp.left.type == "variable" then
|
||||
|
|
@ -113,6 +121,28 @@ local function eval(state, exp)
|
|||
type = "number",
|
||||
value = truthy(right) and 1 or 0
|
||||
}
|
||||
-- conditional
|
||||
elseif exp.type == "~" then
|
||||
local right, righte = eval(state, exp.right)
|
||||
if not right then return right, righte end
|
||||
if truthy(right) then
|
||||
local left, lefte = eval(state, exp.left)
|
||||
if not left then return left, lefte end
|
||||
return left
|
||||
end
|
||||
return {
|
||||
type = "nil",
|
||||
value = nil
|
||||
}
|
||||
-- tag
|
||||
elseif exp.type == "#" then
|
||||
local right, righte = eval(state, exp.right)
|
||||
if not right then return right, righte end
|
||||
local i = tags:push(state, right)
|
||||
local left, lefte = eval(state, exp.left)
|
||||
tags:trim(state, i-1)
|
||||
if not left then return left, lefte end
|
||||
return left
|
||||
-- variable
|
||||
elseif exp.type == "variable" then
|
||||
return get_variable(state, exp.name)
|
||||
|
|
@ -390,7 +420,8 @@ end
|
|||
package.loaded[...] = eval
|
||||
run = require((...):gsub("expression$", "interpreter")).run
|
||||
expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
|
||||
flatten_list = require((...):gsub("interpreter%.expression$", "parser.common")).flatten_list
|
||||
local common = require((...):gsub("expression$", "common"))
|
||||
to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable
|
||||
to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable, common.tags, common.eval_text_callback, common.events
|
||||
|
||||
return eval
|
||||
|
|
|
|||
|
|
@ -1,202 +1,7 @@
|
|||
local eval
|
||||
local truthy, merge_state, to_lua, escape, get_variable, eval_text_callback
|
||||
local truthy, merge_state, escape, get_variable, eval_text_callback, tags, events
|
||||
local run_line, run_block
|
||||
|
||||
local function post_process_text(state, text)
|
||||
-- remove trailing spaces
|
||||
if state.feature_flags["strip trailing spaces"] then
|
||||
local final = text[#text]
|
||||
if final then
|
||||
final.text = final.text:match("^(.-) *$")
|
||||
if final.text == "" then
|
||||
table.remove(text)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- remove duplicate spaces
|
||||
if state.feature_flags["strip duplicate spaces"] then
|
||||
for i=1, #text-1 do
|
||||
local a, b = text[i], text[i+1]
|
||||
local na = #a.text:match(" *$")
|
||||
local nb = #b.text:match("^ *")
|
||||
if na > 0 and nb > 0 then -- remove duplicated spaces from second element first
|
||||
b.text = b.text:match("^ *(.-)$")
|
||||
end
|
||||
if na > 1 then
|
||||
a.text = a.text:match("^(.- ) *$")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- tag management
|
||||
local tags = {
|
||||
--- push new tags on top of the stack, from Anselme values
|
||||
push = function(self, state, val)
|
||||
local new = {}
|
||||
-- copy
|
||||
local last = self:current(state)
|
||||
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)
|
||||
return self:len(state)
|
||||
end,
|
||||
--- same but do not merge with last stack item
|
||||
push_lua_no_merge = function(self, state, val)
|
||||
table.insert(state.interpreter.tags, val)
|
||||
return self:len(state)
|
||||
end,
|
||||
-- pop tag table on top of the stack
|
||||
pop = function(self, state)
|
||||
table.remove(state.interpreter.tags)
|
||||
end,
|
||||
--- return current lua tags table
|
||||
current = function(self, state)
|
||||
return state.interpreter.tags[#state.interpreter.tags] or {}
|
||||
end,
|
||||
--- returns length of tags stack
|
||||
len = function(self, state)
|
||||
return #state.interpreter.tags
|
||||
end,
|
||||
--- pop item until we reached desired stack length
|
||||
-- try to prefer this to pop if possible, so in case we mess up the stack somehow it will restore the stack to a good state
|
||||
-- (we may allow tag push/pop from the user side at some point)
|
||||
trim = function(self, state, len)
|
||||
while #state.interpreter.tags > len do
|
||||
self:pop(state)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
--- event buffer management
|
||||
-- i.e. only for text and choice events
|
||||
local events = {
|
||||
--- add a new element to the event buffer
|
||||
-- will flush if needed
|
||||
-- returns true in case of success
|
||||
-- returns nil, err in case of error
|
||||
append = function(self, state, type, data)
|
||||
if state.interpreter.event_capture_stack[type] then
|
||||
local r, e = state.interpreter.event_capture_stack[type][#state.interpreter.event_capture_stack[type]](data)
|
||||
if not r then return r, e end
|
||||
else
|
||||
local r, e = self:make_space_for(state, type)
|
||||
if not r then return r, e 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)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
--- add a new item in the last element (a list of elements) of the event buffer
|
||||
-- will flush if needed
|
||||
-- will use default or a new list if buffer is empty
|
||||
-- returns true in case of success
|
||||
-- returns nil, err in case of error
|
||||
append_in_last = function(self, state, type, data, default)
|
||||
local r, e = self:make_space_for(state, type)
|
||||
if not r then return r, e end
|
||||
|
||||
if not state.interpreter.event_buffer then
|
||||
r, e = self:append(state, type, default or {})
|
||||
if not r then return r, e end
|
||||
end
|
||||
|
||||
table.insert(state.interpreter.event_buffer[#state.interpreter.event_buffer], data)
|
||||
|
||||
return true
|
||||
end,
|
||||
|
||||
--- start capturing events of a certain type
|
||||
-- when an event of the type is appended, fn will be called with this event data
|
||||
-- and the event will not be added to the event buffer
|
||||
-- fn returns nil, err in case of error
|
||||
push_capture = function(self, state, type, fn)
|
||||
if not state.interpreter.event_capture_stack[type] then
|
||||
state.interpreter.event_capture_stack[type] = {}
|
||||
end
|
||||
table.insert(state.interpreter.event_capture_stack[type], fn)
|
||||
end,
|
||||
--- stop capturing events of a certain type.
|
||||
-- must be called after a push_capture
|
||||
-- this is handled by a stack so nested capturing is allowed.
|
||||
pop_capture = function(self, state, type)
|
||||
table.remove(state.interpreter.event_capture_stack[type])
|
||||
if #state.interpreter.event_capture_stack[type] == 0 then
|
||||
state.interpreter.event_capture_stack[type] = nil
|
||||
end
|
||||
end,
|
||||
|
||||
-- flush event buffer if it's neccessary to push an event of the given type
|
||||
-- returns true in case of success
|
||||
-- returns nil, err in case of error
|
||||
make_space_for = function(self, state, type)
|
||||
if state.interpreter.event_buffer and state.interpreter.event_type ~= type and not state.interpreter.event_capture_stack[type] then
|
||||
return self:flush(state)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
--- flush events and send them to the game if possible
|
||||
-- returns true in case of success
|
||||
-- returns nil, err in case of error
|
||||
flush = function(self, state)
|
||||
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
|
||||
state.interpreter.skip_choices_until_flush = nil
|
||||
-- extract some needed state data for each choice block
|
||||
local choices
|
||||
if type == "choice" then
|
||||
choices = {}
|
||||
for _, c in ipairs(buffer) do
|
||||
table.insert(choices, c._state)
|
||||
c._state = nil
|
||||
end
|
||||
end
|
||||
-- text post processing
|
||||
if type == "text" then
|
||||
post_process_text(state, buffer)
|
||||
end
|
||||
if type == "choice" then
|
||||
for _, c in ipairs(buffer) do
|
||||
post_process_text(state, c)
|
||||
end
|
||||
end
|
||||
-- yield event
|
||||
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 > #choices then
|
||||
return nil, "invalid choice"
|
||||
else
|
||||
local choice = choices[sel]
|
||||
-- execute in expected tag & event capture state
|
||||
local capture_state = state.interpreter.event_capture_stack
|
||||
state.interpreter.event_capture_stack = {}
|
||||
local i = tags:push_lua_no_merge(state, choice.tags)
|
||||
local _, e = run_block(state, choice.block)
|
||||
tags:trim(state, i-1)
|
||||
state.interpreter.event_capture_stack = capture_state
|
||||
if e then return nil, e end
|
||||
-- we discard return value from choice block as the execution is delayed until an event flush
|
||||
-- and we don't want to stop the execution of another function unexpectedly
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
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
|
||||
|
|
@ -236,10 +41,7 @@ run_line = function(state, line)
|
|||
local v2, e2 = events:append_in_last(state, "choice", event, { _state = choice_block_state })
|
||||
if not v2 then return v2, e2 end
|
||||
end)
|
||||
v, e = eval_text_callback(state, line.text, function(text)
|
||||
local v2, e2 = events:append_in_last(state, "choice", { text = text, tags = currentTags }, { _state = choice_block_state })
|
||||
if not v2 then return v2, e2 end
|
||||
end)
|
||||
v, e = eval(state, line.text)
|
||||
events:pop_capture(state, "text")
|
||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
||||
elseif line.type == "tag" then
|
||||
|
|
@ -257,12 +59,7 @@ run_line = function(state, line)
|
|||
elseif line.type == "text" then
|
||||
local v, e = events:make_space_for(state, "text") -- do this before any evaluation start
|
||||
if not v then return v, ("%s; in automatic event flush at %s"):format(e, line.source) end
|
||||
local currentTags = tags:current(state)
|
||||
v, e = eval_text_callback(state, line.text, function(text)
|
||||
-- why you would want to send a non-text event from there, I have no idea, but I'm not going to stop you
|
||||
local v2, e2 = events:append(state, "text", { text = text, tags = currentTags })
|
||||
if not v2 then return v2, e2 end
|
||||
end)
|
||||
v, e = eval(state, line.text)
|
||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
||||
elseif line.type == "flush_events" then
|
||||
local v, e = events:flush(state)
|
||||
|
|
@ -409,7 +206,7 @@ local interpreter = {
|
|||
package.loaded[...] = interpreter
|
||||
eval = require((...):gsub("interpreter$", "expression"))
|
||||
local common = require((...):gsub("interpreter$", "common"))
|
||||
truthy, merge_state, to_lua, get_variable, eval_text_callback = common.truthy, common.merge_state, common.to_lua, common.get_variable, common.eval_text_callback
|
||||
truthy, merge_state, tags, get_variable, eval_text_callback, events = common.truthy, common.merge_state, common.tags, common.get_variable, common.eval_text_callback, common.events
|
||||
escape = require((...):gsub("interpreter%.interpreter$", "parser.common")).escape
|
||||
|
||||
return interpreter
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ common = {
|
|||
-- operators not included here:
|
||||
-- * assignment operators (:=, +=, -=, //=, /=, *=, %=, ^=): handled with its own syntax (function assignment)
|
||||
-- * list operator (,): is used when calling every functions, sounds like more trouble than it's worth
|
||||
-- * | and & oprators: are lazy and don't behave like regular functions
|
||||
-- * |, & and ~ operators: are lazy and don't behave like regular functions
|
||||
-- * # operator: need to set tag state before evaluating the left arg
|
||||
-- * . operator: don't behave like regular functions either
|
||||
";",
|
||||
"!=", "==", ">=", "<=", "<", ">",
|
||||
|
|
@ -45,7 +46,11 @@ common = {
|
|||
["\\\\"] = "\\",
|
||||
["\\\""] = "\"",
|
||||
["\\n"] = "\n",
|
||||
["\\t"] = "\t"
|
||||
["\\t"] = "\t",
|
||||
["\\~"] = "~",
|
||||
["\\#"] = "#",
|
||||
["\\$"] = "$", -- FIXME
|
||||
["\\{"] = "{"
|
||||
},
|
||||
--- escape a string to be used as an exact match pattern
|
||||
escape = function(str)
|
||||
|
|
@ -116,38 +121,90 @@ common = {
|
|||
flatten_list = function(list, t)
|
||||
t = t or {}
|
||||
if list.type == "list" then
|
||||
table.insert(t, list.left)
|
||||
common.flatten_list(list.right, t)
|
||||
table.insert(t, 1, list.right)
|
||||
common.flatten_list(list.left, t)
|
||||
else
|
||||
table.insert(t, list)
|
||||
table.insert(t, 1, list)
|
||||
end
|
||||
return t
|
||||
end,
|
||||
-- parse interpolated expressions in a text
|
||||
-- * list of strings and expressions
|
||||
-- allow_subtext (bool) to enable or not [subtext] support
|
||||
-- if allow_binops is given, if one of the caracters of allow_binops appear unescaped in the text, it will interpreter a binary operator expression
|
||||
-- * returns a text expression, remaining (if the right expression stop before the end of the text)
|
||||
-- if allow_binops is not given:
|
||||
-- * returns a list of strings and expressions (text elements)
|
||||
-- * nil, err: in case of error
|
||||
parse_text = function(text, state, namespace)
|
||||
parse_text = function(text, state, namespace, allow_binops, allow_subtext, in_subtext)
|
||||
local l = {}
|
||||
while text:match("[^%{]+") do
|
||||
local t, e = text:match("^([^%{]*)(.-)$")
|
||||
local text_exp
|
||||
local delimiters = ""
|
||||
if allow_binops then
|
||||
text_exp = { type = "text", text = l }
|
||||
delimiters = allow_binops
|
||||
end
|
||||
if allow_subtext then
|
||||
delimiters = delimiters .. "%["
|
||||
end
|
||||
if in_subtext then
|
||||
delimiters = delimiters .. "%]"
|
||||
end
|
||||
while text:match(("[^{%s]+"):format(delimiters)) do
|
||||
local t, r = text:match(("^([^{%s]*)(.-)$"):format(delimiters))
|
||||
-- text
|
||||
if t ~= "" then table.insert(l, t) end
|
||||
if t ~= "" then
|
||||
-- handle \{ escape: skip to next { until it's not escaped
|
||||
while t:match("\\$") and r:match(("^[{%s]"):format(delimiters)) do
|
||||
local t2, r2 = r:match(("^([{%s][^{%s]*)(.-)$"):format(delimiters, delimiters))
|
||||
t = t:match("^(.-)\\$") .. t2
|
||||
r = r2
|
||||
end
|
||||
-- replace other escape codes
|
||||
local escaped = t:gsub("\\.", common.string_escapes)
|
||||
table.insert(l, escaped)
|
||||
end
|
||||
-- expr
|
||||
if e:match("^{") then
|
||||
local exp, rem = expression(e:gsub("^{", ""), state, namespace)
|
||||
if r:match("^{") then
|
||||
local exp, rem = expression(r: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
|
||||
-- wrap in format() call
|
||||
local variant, err = common.find_function_variant(state, namespace, "{}", exp, true)
|
||||
local variant, err = common.find_function_variant(state, namespace, "{}", { type = "parentheses", expression = exp }, true)
|
||||
if not variant then return variant, err end
|
||||
-- add to text
|
||||
table.insert(l, variant)
|
||||
text = rem:match("^%s*}(.*)$")
|
||||
else
|
||||
-- start subtext
|
||||
elseif allow_subtext and r:match("^%[") then
|
||||
local exp, rem = common.parse_text(r:gsub("^%[", ""), state, namespace, allow_binops, allow_subtext, true)
|
||||
if not exp then return nil, rem end
|
||||
if not rem:match("^%]") then return nil, ("expected closing ] at end of subtext before %q"):format(rem) end
|
||||
-- add to text
|
||||
table.insert(l, exp)
|
||||
text = rem:match("^%](.*)$")
|
||||
-- end subtext
|
||||
elseif in_subtext and r:match("^%]") then
|
||||
if allow_binops then
|
||||
return text_exp, r
|
||||
else
|
||||
return l
|
||||
end
|
||||
-- binop expression at the end of the text
|
||||
elseif allow_binops and r:match(("^[%s]"):format(allow_binops)) then
|
||||
local exp, rem = expression(r, state, namespace, nil, text_exp)
|
||||
if not exp then return nil, rem end
|
||||
return exp, rem
|
||||
elseif r == "" then
|
||||
break
|
||||
else
|
||||
error(("unexpected %q at end of text or string"):format(r))
|
||||
end
|
||||
end
|
||||
return l
|
||||
if allow_binops then
|
||||
return text_exp, ""
|
||||
else
|
||||
return l
|
||||
end
|
||||
end,
|
||||
-- find compatible function variants from a fully qualified name
|
||||
-- this functions does not guarantee that functions are fully compatible with the given arguments and only performs a pre-selection without the ones which definitely aren't
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes
|
||||
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text
|
||||
|
||||
--- binop priority
|
||||
local binops_prio = {
|
||||
[1] = { ";" },
|
||||
[2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" },
|
||||
[3] = { "," },
|
||||
[4] = { "|", "&" },
|
||||
[4] = { "|", "&", "~", "#" },
|
||||
[5] = { "!=", "==", ">=", "<=", "<", ">" },
|
||||
[6] = { "+", "-" },
|
||||
[7] = { "*", "//", "/", "%" },
|
||||
|
|
@ -70,12 +70,6 @@ local function expression(s, state, namespace, current_priority, operating_on)
|
|||
-- parse interpolated expressions
|
||||
local l, e = parse_text(d, state, namespace)
|
||||
if not l then return l, e end
|
||||
-- escape the string parts
|
||||
for j, ls in ipairs(l) do
|
||||
if type(ls) == "string" then
|
||||
l[j] = ls:gsub("\\.", string_escapes)
|
||||
end
|
||||
end
|
||||
return expression(r, state, namespace, current_priority, {
|
||||
type = "string",
|
||||
value = l
|
||||
|
|
@ -191,7 +185,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
|
|||
else
|
||||
-- binop
|
||||
for prio, oplist in ipairs(binops_prio) do
|
||||
if prio >= current_priority then
|
||||
if prio > current_priority then
|
||||
for _, op in ipairs(oplist) do
|
||||
local escaped = escape(op)
|
||||
if s:match("^"..escaped) then
|
||||
|
|
@ -275,7 +269,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
|
|||
left = operating_on,
|
||||
right = right
|
||||
})
|
||||
elseif op == "&" or op == "|" then
|
||||
elseif op == "&" or op == "|" or op == "~" or op == "#" then
|
||||
return expression(r, state, namespace, current_priority, {
|
||||
type = op,
|
||||
left = operating_on,
|
||||
|
|
@ -287,8 +281,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
|
|||
local args = {
|
||||
type = "list",
|
||||
left = operating_on,
|
||||
-- wrap in parentheses to avoid appending to argument list if right is a list
|
||||
right = { type = "parentheses", expression = right }
|
||||
right = right
|
||||
}
|
||||
local variant, err = find_function_variant(state, namespace, op, args, true)
|
||||
if not variant then return variant, err end
|
||||
|
|
@ -318,6 +311,6 @@ end
|
|||
|
||||
package.loaded[...] = expression
|
||||
local common = require((...):gsub("expression$", "common"))
|
||||
identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text, common.string_escapes
|
||||
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
|
||||
|
|
|
|||
|
|
@ -55,10 +55,11 @@ local function parse(state)
|
|||
state.variables[line.fqm].value.expression = line.expression
|
||||
end
|
||||
end
|
||||
-- text
|
||||
-- text (text & choice lines)
|
||||
if line.text then
|
||||
local txt, err = parse_text(line.text, state, namespace)
|
||||
if err then return nil, ("%s; at %s"):format(err, line.source) end
|
||||
local txt, err = parse_text(line.text, state, namespace, "#~", true)
|
||||
if not txt then return nil, ("%s; at %s"):format(err, line.source) end
|
||||
if err:match("[^%s]") then return nil, ("expected end of expression in end-of-text expression before %q"):format(err) end
|
||||
line.text = txt
|
||||
end
|
||||
state.queued_lines[i] = nil
|
||||
|
|
|
|||
|
|
@ -325,18 +325,8 @@ local function transform_indented(indented)
|
|||
if l.content:match("^%(") then
|
||||
table.remove(indented, i)
|
||||
else
|
||||
-- condition decorator
|
||||
if l.content:match("^.-[^~]%~[^#~$]-$") then
|
||||
local decorator
|
||||
l.content, decorator = l.content:match("^(..-)(%~[^#~$]-)$")
|
||||
indented[i] = { content = decorator, source = l.source, children = { l } }
|
||||
-- tag decorator
|
||||
elseif l.content:match("^..-%#[^#~$]-$") then
|
||||
local decorator
|
||||
l.content, decorator = l.content:match("^(..-)(%#[^#~$]-)$")
|
||||
indented[i] = { content = decorator, source = l.source, children = { l } }
|
||||
-- function decorator
|
||||
elseif l.content:match("^..-%$[^#~$]-$") then
|
||||
if l.content:match("^.-[^\\]%$[^#~$]-$") then -- FIXME
|
||||
local name
|
||||
l.content, name = l.content:match("^(..-)%$([^#~$]-)$")
|
||||
indented[i] = { content = "~"..name, source = l.source }
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ if args.help then
|
|||
print("")
|
||||
print("For script or game mode:")
|
||||
print(" --lang code: load a language file")
|
||||
print(" --save: print VM state at the end of the script")
|
||||
print(" --save: print save data at the end of the script")
|
||||
os.exit()
|
||||
end
|
||||
|
||||
|
|
|
|||
35
test/tests/choice with decorators.ans
Normal file
35
test/tests/choice with decorators.ans
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> a ~ 1
|
||||
-> a
|
||||
> b
|
||||
-> b
|
||||
~ choose(1)
|
||||
|
||||
> a ~ 1
|
||||
-> a
|
||||
> b
|
||||
-> b
|
||||
~ choose(2)
|
||||
|
||||
> a ~ 0
|
||||
-> a
|
||||
> b
|
||||
-> b
|
||||
~ choose(1)
|
||||
|
||||
> a
|
||||
-> a
|
||||
> b # 25
|
||||
-> b
|
||||
~ choose(2)
|
||||
|
||||
> a ~ 0 # 12
|
||||
-> a
|
||||
> b # 3
|
||||
-> b
|
||||
~ choose(1)
|
||||
|
||||
> a ~ 1 # 12
|
||||
-> a
|
||||
> b # 3
|
||||
-> b
|
||||
~ choose(1)
|
||||
132
test/tests/choice with decorators.lua
Normal file
132
test/tests/choice with decorators.lua
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
local _={}
|
||||
_[67]={3}
|
||||
_[66]={12}
|
||||
_[65]={3}
|
||||
_[64]={25}
|
||||
_[63]={}
|
||||
_[62]={}
|
||||
_[61]={}
|
||||
_[60]={}
|
||||
_[59]={}
|
||||
_[58]={}
|
||||
_[57]={}
|
||||
_[56]={text="b",tags=_[67]}
|
||||
_[55]={text="a",tags=_[66]}
|
||||
_[54]={}
|
||||
_[53]={text="b",tags=_[65]}
|
||||
_[52]={}
|
||||
_[51]={text="b",tags=_[64]}
|
||||
_[50]={text="a",tags=_[63]}
|
||||
_[49]={}
|
||||
_[48]={text="b",tags=_[62]}
|
||||
_[47]={}
|
||||
_[46]={text="b",tags=_[61]}
|
||||
_[45]={text="a",tags=_[60]}
|
||||
_[44]={}
|
||||
_[43]={text="b",tags=_[59]}
|
||||
_[42]={text="a",tags=_[58]}
|
||||
_[41]={text="-> a",tags=_[57]}
|
||||
_[40]={_[56]}
|
||||
_[39]={_[55]}
|
||||
_[38]={text="-> b",tags=_[54]}
|
||||
_[37]={_[53]}
|
||||
_[36]={text="-> b",tags=_[52]}
|
||||
_[35]={_[51]}
|
||||
_[34]={_[50]}
|
||||
_[33]={text="-> b",tags=_[49]}
|
||||
_[32]={_[48]}
|
||||
_[31]={text="-> b",tags=_[47]}
|
||||
_[30]={_[46]}
|
||||
_[29]={_[45]}
|
||||
_[28]={text="-> a",tags=_[44]}
|
||||
_[27]={_[43]}
|
||||
_[26]={_[42]}
|
||||
_[25]={_[41]}
|
||||
_[24]={_[39],_[40]}
|
||||
_[23]={_[38]}
|
||||
_[22]={_[37]}
|
||||
_[21]={_[36]}
|
||||
_[20]={_[34],_[35]}
|
||||
_[19]={_[33]}
|
||||
_[18]={_[32]}
|
||||
_[17]={_[31]}
|
||||
_[16]={_[29],_[30]}
|
||||
_[15]={_[28]}
|
||||
_[14]={_[26],_[27]}
|
||||
_[13]={"return"}
|
||||
_[12]={"text",_[25]}
|
||||
_[11]={"choice",_[24]}
|
||||
_[10]={"text",_[23]}
|
||||
_[9]={"choice",_[22]}
|
||||
_[8]={"text",_[21]}
|
||||
_[7]={"choice",_[20]}
|
||||
_[6]={"text",_[19]}
|
||||
_[5]={"choice",_[18]}
|
||||
_[4]={"text",_[17]}
|
||||
_[3]={"choice",_[16]}
|
||||
_[2]={"text",_[15]}
|
||||
_[1]={"choice",_[14]}
|
||||
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13]}
|
||||
--[[
|
||||
{ "choice", { { {
|
||||
tags = {},
|
||||
text = "a"
|
||||
} }, { {
|
||||
tags = {},
|
||||
text = "b"
|
||||
} } } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "-> a"
|
||||
} } }
|
||||
{ "choice", { { {
|
||||
tags = {},
|
||||
text = "a"
|
||||
} }, { {
|
||||
tags = {},
|
||||
text = "b"
|
||||
} } } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "-> b"
|
||||
} } }
|
||||
{ "choice", { { {
|
||||
tags = {},
|
||||
text = "b"
|
||||
} } } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "-> b"
|
||||
} } }
|
||||
{ "choice", { { {
|
||||
tags = {},
|
||||
text = "a"
|
||||
} }, { {
|
||||
tags = { 25 },
|
||||
text = "b"
|
||||
} } } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "-> b"
|
||||
} } }
|
||||
{ "choice", { { {
|
||||
tags = { 3 },
|
||||
text = "b"
|
||||
} } } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "-> b"
|
||||
} } }
|
||||
{ "choice", { { {
|
||||
tags = { 12 },
|
||||
text = "a"
|
||||
} }, { {
|
||||
tags = { 3 },
|
||||
text = "b"
|
||||
} } } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "-> a"
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
ko ~ 0
|
||||
ok ~ 1
|
||||
ok bis ~
|
||||
ok bis ~ 1
|
||||
6
test/tests/condition operator.ans
Normal file
6
test/tests/condition operator.ans
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
$ f
|
||||
b
|
||||
|
||||
a {f ~ 5} c
|
||||
|
||||
a {f ~ 0} c
|
||||
35
test/tests/condition operator.lua
Normal file
35
test/tests/condition operator.lua
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
local _={}
|
||||
_[13]={}
|
||||
_[12]={}
|
||||
_[11]={}
|
||||
_[10]={tags=_[13],text="c"}
|
||||
_[9]={tags=_[13],text="a "}
|
||||
_[8]={tags=_[11],text=" c"}
|
||||
_[7]={tags=_[12],text="b"}
|
||||
_[6]={tags=_[11],text="a "}
|
||||
_[5]={_[9],_[10]}
|
||||
_[4]={_[6],_[7],_[8]}
|
||||
_[3]={"return"}
|
||||
_[2]={"text",_[5]}
|
||||
_[1]={"text",_[4]}
|
||||
return {_[1],_[2],_[3]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = <1>{},
|
||||
text = "a "
|
||||
}, {
|
||||
tags = {},
|
||||
text = "b"
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = " c"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = <1>{},
|
||||
text = "a "
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = "c"
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
3
test/tests/remove duplicate spaces.ans
Normal file
3
test/tests/remove duplicate spaces.ans
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
a
|
||||
b
|
||||
{" "}c
|
||||
28
test/tests/remove duplicate spaces.lua
Normal file
28
test/tests/remove duplicate spaces.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
local _={}
|
||||
_[10]={}
|
||||
_[9]={}
|
||||
_[8]={}
|
||||
_[7]={text="c",tags=_[10]}
|
||||
_[6]={text="",tags=_[10]}
|
||||
_[5]={text="b ",tags=_[9]}
|
||||
_[4]={text="a ",tags=_[8]}
|
||||
_[3]={_[4],_[5],_[6],_[7]}
|
||||
_[2]={"return"}
|
||||
_[1]={"text",_[3]}
|
||||
return {_[1],_[2]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "a "
|
||||
}, {
|
||||
tags = {},
|
||||
text = "b "
|
||||
}, {
|
||||
tags = <1>{},
|
||||
text = ""
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = "c"
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
5
test/tests/remove trailing spaces.ans
Normal file
5
test/tests/remove trailing spaces.ans
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
a
|
||||
|
||||
b
|
||||
|
||||
{" "}c
|
||||
34
test/tests/remove trailing spaces.lua
Normal file
34
test/tests/remove trailing spaces.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
local _={}
|
||||
_[14]={}
|
||||
_[13]={}
|
||||
_[12]={}
|
||||
_[11]={tags=_[14],text="c"}
|
||||
_[10]={tags=_[14],text=" "}
|
||||
_[9]={tags=_[13],text="b"}
|
||||
_[8]={tags=_[12],text="a"}
|
||||
_[7]={_[10],_[11]}
|
||||
_[6]={_[9]}
|
||||
_[5]={_[8]}
|
||||
_[4]={"return"}
|
||||
_[3]={"text",_[7]}
|
||||
_[2]={"text",_[6]}
|
||||
_[1]={"text",_[5]}
|
||||
return {_[1],_[2],_[3],_[4]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "a"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "b"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = <1>{},
|
||||
text = " "
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = "c"
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
$ f
|
||||
# "a":"a"
|
||||
a
|
||||
~ 1 # "b":"b"
|
||||
§ p
|
||||
b # "c":"c"
|
||||
~ 1 # "x":"x"
|
||||
# "b":"b"
|
||||
§ p
|
||||
b # "c":"c"
|
||||
|
||||
c
|
||||
c
|
||||
|
||||
d
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ expression {"{"a"}"}
|
|||
|
||||
quote {"\""}
|
||||
|
||||
other codes {"\n"} {"\\"} {"\t"}
|
||||
other codes {"\n"} {"\\"} {"\t"} \{braces}
|
||||
|
||||
{"escaping expressions {"a"+"bc"} and stuff \\ and quotes \""}
|
||||
{"escaping expressions {"a"+"bc"} and \{stuff} \\ and quotes \""}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
local _={}
|
||||
_[25]={}
|
||||
_[24]={}
|
||||
_[23]={}
|
||||
_[22]={}
|
||||
_[21]={}
|
||||
_[20]={tags=_[24],text="escaping expressions abc and stuff \\ and quotes \""}
|
||||
_[19]={tags=_[23],text="\9"}
|
||||
_[18]={tags=_[23],text=" "}
|
||||
_[17]={tags=_[23],text="\\"}
|
||||
_[16]={tags=_[23],text=" "}
|
||||
_[15]={tags=_[23],text="\n"}
|
||||
_[14]={tags=_[23],text="other codes "}
|
||||
_[13]={tags=_[22],text="\""}
|
||||
_[12]={tags=_[22],text="quote "}
|
||||
_[11]={tags=_[21],text="a"}
|
||||
_[10]={tags=_[21],text="expression "}
|
||||
_[9]={_[20]}
|
||||
_[8]={_[14],_[15],_[16],_[17],_[18],_[19]}
|
||||
_[21]={text="escaping expressions abc and {stuff} \\ and quotes \"",tags=_[25]}
|
||||
_[20]={text=" {braces}",tags=_[24]}
|
||||
_[19]={text="\9",tags=_[24]}
|
||||
_[18]={text=" ",tags=_[24]}
|
||||
_[17]={text="\\",tags=_[24]}
|
||||
_[16]={text=" ",tags=_[24]}
|
||||
_[15]={text="\n",tags=_[24]}
|
||||
_[14]={text="other codes ",tags=_[24]}
|
||||
_[13]={text="\"",tags=_[23]}
|
||||
_[12]={text="quote ",tags=_[23]}
|
||||
_[11]={text="a",tags=_[22]}
|
||||
_[10]={text="expression ",tags=_[22]}
|
||||
_[9]={_[21]}
|
||||
_[8]={_[14],_[15],_[16],_[17],_[18],_[19],_[20]}
|
||||
_[7]={_[12],_[13]}
|
||||
_[6]={_[10],_[11]}
|
||||
_[5]={"return"}
|
||||
|
|
@ -57,10 +58,13 @@ return {_[1],_[2],_[3],_[4],_[5]}
|
|||
}, {
|
||||
tags = <table 1>,
|
||||
text = "\t"
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = " {braces}"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = 'escaping expressions abc and stuff \\ and quotes "'
|
||||
text = 'escaping expressions abc and {stuff} \\ and quotes "'
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
12
test/tests/subtext.ans
Normal file
12
test/tests/subtext.ans
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
$ button
|
||||
A # 2:2
|
||||
|
||||
Press
|
||||
A # 5
|
||||
to jump.
|
||||
|
||||
Press [A#5] to jump.
|
||||
|
||||
Press [{button}#1] to jump.
|
||||
|
||||
Press [-[button#3:3]-#1] to jump.
|
||||
86
test/tests/subtext.lua
Normal file
86
test/tests/subtext.lua
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
local _={}
|
||||
_[33]={1,[3]=3}
|
||||
_[32]={1}
|
||||
_[31]={}
|
||||
_[30]={1,2}
|
||||
_[29]={}
|
||||
_[28]={5}
|
||||
_[27]={}
|
||||
_[26]={}
|
||||
_[25]={5}
|
||||
_[24]={}
|
||||
_[23]={tags=_[31],text=" to jump."}
|
||||
_[22]={tags=_[32],text="-"}
|
||||
_[21]={tags=_[33],text="button"}
|
||||
_[20]={tags=_[32],text="-"}
|
||||
_[19]={tags=_[31],text="Press "}
|
||||
_[18]={tags=_[29],text="to jump."}
|
||||
_[17]={tags=_[30],text="A "}
|
||||
_[16]={tags=_[29],text="Press "}
|
||||
_[15]={tags=_[27],text=" to jump."}
|
||||
_[14]={tags=_[28],text="A"}
|
||||
_[13]={tags=_[27],text="Press "}
|
||||
_[12]={tags=_[26],text="to jump."}
|
||||
_[11]={tags=_[25],text="A "}
|
||||
_[10]={tags=_[24],text="Press "}
|
||||
_[9]={_[19],_[20],_[21],_[22],_[23]}
|
||||
_[8]={_[16],_[17],_[18]}
|
||||
_[7]={_[13],_[14],_[15]}
|
||||
_[6]={_[10],_[11],_[12]}
|
||||
_[5]={"return"}
|
||||
_[4]={"text",_[9]}
|
||||
_[3]={"text",_[8]}
|
||||
_[2]={"text",_[7]}
|
||||
_[1]={"text",_[6]}
|
||||
return {_[1],_[2],_[3],_[4],_[5]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "Press "
|
||||
}, {
|
||||
tags = { 5 },
|
||||
text = "A "
|
||||
}, {
|
||||
tags = {},
|
||||
text = "to jump."
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = <1>{},
|
||||
text = "Press "
|
||||
}, {
|
||||
tags = { 5 },
|
||||
text = "A"
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = " to jump."
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = <1>{},
|
||||
text = "Press "
|
||||
}, {
|
||||
tags = { 1, 2 },
|
||||
text = "A "
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = "to jump."
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = <1>{},
|
||||
text = "Press "
|
||||
}, {
|
||||
tags = <2>{ 1 },
|
||||
text = "-"
|
||||
}, {
|
||||
tags = { 1,
|
||||
[3] = 3
|
||||
},
|
||||
text = "button"
|
||||
}, {
|
||||
tags = <table 2>,
|
||||
text = "-"
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = " to jump."
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# 1
|
||||
foo
|
||||
~ 1 #
|
||||
bar
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
local _={}
|
||||
_[7]={1}
|
||||
_[6]={1}
|
||||
_[5]={tags=_[7],text="bar"}
|
||||
_[4]={tags=_[6],text="foo"}
|
||||
_[3]={_[4],_[5]}
|
||||
_[2]={"return"}
|
||||
_[1]={"text",_[3]}
|
||||
return {_[1],_[2]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = { 1 },
|
||||
text = "foo"
|
||||
}, {
|
||||
tags = { 1 },
|
||||
text = "bar"
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# 1
|
||||
foo
|
||||
~ 1 # "a": [2,3]
|
||||
bar
|
||||
~ 1 # "b": [1,2]
|
||||
bar ~ 1 # "a": [2,3]
|
||||
|
|
|
|||
7
test/tests/tag operator.ans
Normal file
7
test/tests/tag operator.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
$ f
|
||||
b
|
||||
|
||||
a {f # 5} c
|
||||
|
||||
# 2:2
|
||||
a {f # 5} c
|
||||
42
test/tests/tag operator.lua
Normal file
42
test/tests/tag operator.lua
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
local _={}
|
||||
_[15]={5,2}
|
||||
_[14]={[2]=2}
|
||||
_[13]={5}
|
||||
_[12]={}
|
||||
_[11]={tags=_[14],text=" c"}
|
||||
_[10]={tags=_[15],text="b"}
|
||||
_[9]={tags=_[14],text="a "}
|
||||
_[8]={tags=_[12],text=" c"}
|
||||
_[7]={tags=_[13],text="b"}
|
||||
_[6]={tags=_[12],text="a "}
|
||||
_[5]={_[9],_[10],_[11]}
|
||||
_[4]={_[6],_[7],_[8]}
|
||||
_[3]={"return"}
|
||||
_[2]={"text",_[5]}
|
||||
_[1]={"text",_[4]}
|
||||
return {_[1],_[2],_[3]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = <1>{},
|
||||
text = "a "
|
||||
}, {
|
||||
tags = { 5 },
|
||||
text = "b"
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = " c"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = <1>{
|
||||
[2] = 2
|
||||
},
|
||||
text = "a "
|
||||
}, {
|
||||
tags = { 5, 2 },
|
||||
text = "b"
|
||||
}, {
|
||||
tags = <table 1>,
|
||||
text = " c"
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
7
test/tests/text escaping.ans
Normal file
7
test/tests/text escaping.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
expression \{a}
|
||||
|
||||
quote \"
|
||||
|
||||
other codes \n \\ \t
|
||||
|
||||
decorators \# tag \~ condition \$ fn
|
||||
38
test/tests/text escaping.lua
Normal file
38
test/tests/text escaping.lua
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
local _={}
|
||||
_[17]={}
|
||||
_[16]={}
|
||||
_[15]={}
|
||||
_[14]={}
|
||||
_[13]={tags=_[17],text="decorators # tag ~ condition $ fn"}
|
||||
_[12]={tags=_[16],text="other codes \n \\ \9"}
|
||||
_[11]={tags=_[15],text="quote \""}
|
||||
_[10]={tags=_[14],text="expression {a}"}
|
||||
_[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", { {
|
||||
tags = {},
|
||||
text = "expression {a}"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = 'quote "'
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "other codes \n \\ \t"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "decorators # tag ~ condition $ fn"
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
Loading…
Add table
Add a link
Reference in a new issue