1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00

Add while loop line and operator

This commit is contained in:
Étienne Fildadut 2021-12-12 17:07:50 +01:00
parent f5382d2912
commit 48cabbf4c0
14 changed files with 500 additions and 61 deletions

View file

@ -93,6 +93,24 @@ There's different types of lines, depending on their first character(s) (after i
This is.
```
* `~?`: while loop line. Works like `~` condition lines, but if the expression evaluates to true and after it ran its children, will reevaluate the expression again and repeat the previous logic until the expression eventually evaluates to false.
```
(Count to 10:)
:i = 1
~? i < 10
{i}
~ i += 1
(else-conditions can be used if the expression has never evalated to true in the loop:)
~ i := 5
~? i < 2
Never happens.
~~
This is run.
```
* `>`: 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.
```
@ -105,7 +123,7 @@ $ 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.
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)
@ -199,7 +217,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:
```
(binary operator names: _op_)
@ -311,7 +329,7 @@ this is some text.
And this is more text, in a different event.
```
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.
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)
@ -795,10 +813,10 @@ Please also be aware that when resuming from a checkpoint, Anselme will try to r
From lowest to highest priority:
```
_;_
_;_ _;
_:=_ _+=_ _-=_ _//=_ _/=_ _*=_ _%=_ _^=_
_,_
_|_ _&_ _~_ _#_
_|_ _&_ _~?_ _~_ _#_
_!=_ _==_ _>=_ _<=_ _<_ _>_
_+_ _-_
_*_ _//_ _/_ _%_
@ -861,6 +879,10 @@ This only works on strings:
`a | b`: or operator, lazy
`a ~ b`: evaluates b, if true evaluates a and returns it, otherwise returns nil (lazy).
`a ~? b`: evaluates b, if true evaluates a then reevaluate b and loop this until b is false; returns a list containing all successive values that a returned.
##### Functions and function references
`fn(args)`: call the function, checkpoint or function reference with the given arguments.
@ -887,8 +909,6 @@ 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 a and returns it, otherwise returns nil (lazy).
`a # b`: evaluates b, then evaluates a whith b added to the active tags. Returns a.
`a.b`: if a is a function reference, returns the variable named `b` in the referenced function namespace. When overloading this operator, if `b` is an identifier, the operator will interpret it as a string (and not returns the evaluated value of the variable eventually associated to the identifier).

View file

@ -122,6 +122,23 @@ local function eval(state, exp)
type = "nil",
value = nil
}
-- while loop
elseif exp.type == "~?" then
local right, righte = eval(state, exp.right)
if not right then return right, righte end
local l = {}
while truthy(right) do
local left, lefte = eval(state, exp.left)
if not left then return left, lefte end
table.insert(l, left)
-- next iteration
right, righte = eval(state, exp.right)
if not right then return right, righte end
end
return {
type = "list",
value = l
}
-- tag
elseif exp.type == "#" then
local right, righte = eval(state, exp.right)

View file

@ -30,40 +30,56 @@ run_line = function(state, line)
if v then return v end
end
end
elseif line.type == "while" then
line.parent_block.last_condition_success = nil
local v, e = eval(state, line.expression)
if not v then return v, ("%s; at %s"):format(e, line.source) end
while truthy(v) do
line.parent_block.last_condition_success = true
v, e = run_block(state, line.child)
if e then return v, e end
if v then return v end
-- next iteration
v, e = eval(state, line.expression)
if not v then return v, ("%s; at %s"):format(e, line.source) end
end
elseif line.type == "choice" then
local v, e = events:make_space_for(state, "choice")
if not v then return v, ("%s; in automatic event flush at %s"):format(e, line.source) end
v, e = eval(state, line.text)
if not v then return v, ("%s; at %s"):format(e, line.source) end
local l = v.type == "list" and v.value or { v }
-- convert text events to choices
if v.type == "event buffer" then
local current_tags = tags:current(state)
local choice_block_state = { tags = current_tags, block = line.child }
local final_buffer = {}
for _, event in ipairs(v.value) do
if event.type == "text" then
-- create new choice block if needed
local last_choice_block = final_buffer[#final_buffer]
if not last_choice_block or last_choice_block.type ~= "choice" then
last_choice_block = { type = "choice", value = {} }
table.insert(final_buffer, last_choice_block)
for _, item in ipairs(l) do
if item.type == "event buffer" then
local current_tags = tags:current(state)
local choice_block_state = { tags = current_tags, block = line.child }
local final_buffer = {}
for _, event in ipairs(item.value) do
if event.type == "text" then
-- create new choice block if needed
local last_choice_block = final_buffer[#final_buffer]
if not last_choice_block or last_choice_block.type ~= "choice" then
last_choice_block = { type = "choice", value = {} }
table.insert(final_buffer, last_choice_block)
end
-- create new choice item in choice block if needed
local last_choice = last_choice_block.value[#last_choice_block.value]
if not last_choice then
last_choice = { _state = choice_block_state }
table.insert(last_choice_block.value, last_choice)
end
-- add text to last choice item
for _, txt in ipairs(event.value) do
table.insert(last_choice, txt)
end
else
table.insert(final_buffer, event)
end
-- create new choice item in choice block if needed
local last_choice = last_choice_block.value[#last_choice_block.value]
if not last_choice then
last_choice = { _state = choice_block_state }
table.insert(last_choice_block.value, last_choice)
end
-- add text to last choice item
for _, txt in ipairs(event.value) do
table.insert(last_choice, txt)
end
else
table.insert(final_buffer, event)
end
local iv, ie = events:write_buffer(state, final_buffer)
if not iv then return iv, ("%s; at %s"):format(ie, line.source) end
end
v, e = events:write_buffer(state, final_buffer)
if not v then return v, ("%s; at %s"):format(e, line.source) end
end
elseif line.type == "tag" then
local v, e = eval(state, line.expression)
@ -82,9 +98,12 @@ run_line = function(state, line)
if not v then return v, ("%s; in automatic event flush at %s"):format(e, line.source) end
v, e = eval(state, line.text)
if not v then return v, ("%s; at %s"):format(e, line.source) end
if v.type == "event buffer" then
v, e = events:write_buffer(state, v.value)
if not v then return v, ("%s; at %s"):format(e, line.source) end
local l = v.type == "list" and v.value or { v }
for _, item in ipairs(l) do
if item.type == "event buffer" then
local iv, ie = events:write_buffer(state, item.value)
if not iv then return iv, ("%s; at %s"):format(ie, line.source) end
end
end
elseif line.type == "flush_events" then
local v, e = events:flush(state)

View file

@ -43,8 +43,6 @@ Reserved symbols that are still not used as a line type: `^+-=</[]*{}|\_!?.,;)"&
Broad goals and ideas that may never be implemented. Mostly used as personal post-it notes.
TODO: a way to make loops
TODO: consider something like :identifier to create a string "identifier", might simplify the identifier=value special syntax and free up the = operator
TODO: some sensible way to capture text event in string litterals (string interpolation)? -> meh, this means we can capture choice events, and choice events contain their code block, and we don't want to store code blocks in the save file (as code can be updated/translated/whatever)
@ -74,7 +72,7 @@ TODO: fn/checkpoint/tag: maybe consider them a regular func call that takes chil
# color:red
a
translate to
translate to something like
~ tag.push(color:red)
a
@ -89,7 +87,7 @@ TODO: make language simple enough to be able to reimplement it in, say, nim. Esp
TODO: test reacheability of script paths
TODO: redisign the checkpoint system to work better when used with parallel scripts (if both change the same variable, will be overwritten)
TODO: redisign the checkpoint system to work better when used with parallel scripts (if both change the same variable, will be overwritten); not sure how to do that, would need some complicated conflict resolution code or something like CRDT...
TODO: redisign a static type checking system
If we want to go full gradual typing, it would help to:

View file

@ -31,7 +31,7 @@ common = {
-- operators not included here and why:
-- * 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 ~ operators: 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
-- prefix unop

View file

@ -5,7 +5,7 @@ local binops_prio = {
[1] = { ";" },
[2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" },
[3] = { "," },
[4] = { "|", "&", "~", "#" },
[4] = { "|", "&", "~?", "~", "#" },
[5] = { "!=", "==", ">=", "<=", "<", ">" },
[6] = { "+", "-" },
[7] = { "*", "//", "/", "%" },
@ -336,7 +336,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
left = operating_on,
right = right
})
elseif op == "&" or op == "|" or op == "~" or op == "#" then
elseif op == "&" or op == "|" or op == "~?" or op == "~" or op == "#" then
return expression(r, state, namespace, current_priority, {
type = op,
left = operating_on,

View file

@ -29,11 +29,17 @@ local function parse_line(line, state, namespace)
local r = {
source = line.source
}
-- else-condition & condition
if l:match("^~~?") then
r.type = l:match("^~~") and "else-condition" or "condition"
-- else-condition, condition & while
if l:match("^~[~%?]?") then
if l:match("^~~") then
r.type = "else-condition"
elseif l:match("^~%?") then
r.type = "while"
else
r.type = "condition"
end
r.child = true
local expr = l:match("^~~?(.*)$")
local expr = l:match("^~[~%?]?(.*)$")
if expr:match("[^%s]") then
r.expression = expr
else

View file

@ -308,24 +308,14 @@ $ random(l...)
~ l(rand(1, l!len))!
$ next(l...)
:f = l(len(l))
$ find first not seen(j)
~ l(j).👁 == 0
~ f := l(j)
~~ j < len(l)
~ find first not seen(j+1)
~ find first not seen(1)
~ f!
:j = 0
~? j += 1; j < len(l) & l(j).👁 != 0
~ l(j)!
$ cycle(l...)
:f = l(1)
$ find first smaller(j)
~ l(j).👁 < f.👁
~ f := l(j)
~~ j < len(l)
~ find first smaller(j+1)
~ len(l) > 1
~ find first smaller(2)
:j = 1
~? j += 1; j <= len(l) & !((f := l(j); 1) ~ l(j).👁 < f.👁)
~ f!
]]

View file

@ -0,0 +1,5 @@
:i = 0
{i}\n~? (i += 1; i <= 10)
{i}

View file

@ -0,0 +1,117 @@
local _={}
_[47]={}
_[46]={}
_[45]={}
_[44]={}
_[43]={}
_[42]={}
_[41]={}
_[40]={}
_[39]={}
_[38]={}
_[37]={}
_[36]={}
_[35]={}
_[34]={}
_[33]={}
_[32]={}
_[31]={}
_[30]={}
_[29]={}
_[28]={}
_[27]={}
_[26]={text="11",tags=_[47]}
_[25]={text="\n",tags=_[46]}
_[24]={text="10",tags=_[45]}
_[23]={text="\n",tags=_[44]}
_[22]={text="9",tags=_[43]}
_[21]={text="\n",tags=_[42]}
_[20]={text="8",tags=_[41]}
_[19]={text="\n",tags=_[40]}
_[18]={text="7",tags=_[39]}
_[17]={text="\n",tags=_[38]}
_[16]={text="6",tags=_[37]}
_[15]={text="\n",tags=_[36]}
_[14]={text="5",tags=_[35]}
_[13]={text="\n",tags=_[34]}
_[12]={text="4",tags=_[33]}
_[11]={text="\n",tags=_[32]}
_[10]={text="3",tags=_[31]}
_[9]={text="\n",tags=_[30]}
_[8]={text="2",tags=_[29]}
_[7]={text="\n",tags=_[28]}
_[6]={text="1",tags=_[27]}
_[5]={_[26]}
_[4]={_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15],_[16],_[17],_[18],_[19],_[20],_[21],_[22],_[23],_[24],_[25]}
_[3]={"return"}
_[2]={"text",_[5]}
_[1]={"text",_[4]}
return {_[1],_[2],_[3]}
--[[
{ "text", { {
tags = {},
text = "1"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "2"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "3"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "4"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "5"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "6"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "7"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "8"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "9"
}, {
tags = {},
text = "\n"
}, {
tags = {},
text = "10"
}, {
tags = {},
text = "\n"
} } }
{ "text", { {
tags = {},
text = "11"
} } }
{ "return" }
]]--

View file

@ -0,0 +1,19 @@
:i = 1
Start with i={i}:
~? i < 5
{i}
~ i += 1
~~
Loop not ran.
Start with i={i}:
~? i < 5
{i}
~ i += 1
~~
Loop not ran.

View file

@ -0,0 +1,82 @@
local _={}
_[37]={}
_[36]={}
_[35]={}
_[34]={}
_[33]={}
_[32]={}
_[31]={}
_[30]={}
_[29]={}
_[28]={}
_[27]={}
_[26]={text="Loop not ran.",tags=_[37]}
_[25]={text=":",tags=_[36]}
_[24]={text="5",tags=_[35]}
_[23]={text="Start with i=",tags=_[34]}
_[22]={text="4",tags=_[33]}
_[21]={text="3",tags=_[32]}
_[20]={text="2",tags=_[31]}
_[19]={text="1",tags=_[30]}
_[18]={text=":",tags=_[29]}
_[17]={text="1",tags=_[28]}
_[16]={text="Start with i=",tags=_[27]}
_[15]={_[26]}
_[14]={_[23],_[24],_[25]}
_[13]={_[22]}
_[12]={_[21]}
_[11]={_[20]}
_[10]={_[19]}
_[9]={_[16],_[17],_[18]}
_[8]={"return"}
_[7]={"text",_[15]}
_[6]={"text",_[14]}
_[5]={"text",_[13]}
_[4]={"text",_[12]}
_[3]={"text",_[11]}
_[2]={"text",_[10]}
_[1]={"text",_[9]}
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8]}
--[[
{ "text", { {
tags = {},
text = "Start with i="
}, {
tags = {},
text = "1"
}, {
tags = {},
text = ":"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "2"
} } }
{ "text", { {
tags = {},
text = "3"
} } }
{ "text", { {
tags = {},
text = "4"
} } }
{ "text", { {
tags = {},
text = "Start with i="
}, {
tags = {},
text = "5"
}, {
tags = {},
text = ":"
} } }
{ "text", { {
tags = {},
text = "Loop not ran."
} } }
{ "return" }
]]--

16
test/tests/while loop.ans Normal file
View file

@ -0,0 +1,16 @@
:i = 0
~? i <= 10
{i}
~ i += 1
return in loop:
~ i := 0
~? i <= 10
{i}
~ i == 5
@
~ i += 1

150
test/tests/while loop.lua Normal file
View file

@ -0,0 +1,150 @@
local _={}
_[73]={}
_[72]={}
_[71]={}
_[70]={}
_[69]={}
_[68]={}
_[67]={}
_[66]={}
_[65]={}
_[64]={}
_[63]={}
_[62]={}
_[61]={}
_[60]={}
_[59]={}
_[58]={}
_[57]={}
_[56]={}
_[55]={text="5",tags=_[73]}
_[54]={text="4",tags=_[72]}
_[53]={text="3",tags=_[71]}
_[52]={text="2",tags=_[70]}
_[51]={text="1",tags=_[69]}
_[50]={text="0",tags=_[68]}
_[49]={text="return in loop:",tags=_[67]}
_[48]={text="10",tags=_[66]}
_[47]={text="9",tags=_[65]}
_[46]={text="8",tags=_[64]}
_[45]={text="7",tags=_[63]}
_[44]={text="6",tags=_[62]}
_[43]={text="5",tags=_[61]}
_[42]={text="4",tags=_[60]}
_[41]={text="3",tags=_[59]}
_[40]={text="2",tags=_[58]}
_[39]={text="1",tags=_[57]}
_[38]={text="0",tags=_[56]}
_[37]={_[55]}
_[36]={_[54]}
_[35]={_[53]}
_[34]={_[52]}
_[33]={_[51]}
_[32]={_[50]}
_[31]={_[49]}
_[30]={_[48]}
_[29]={_[47]}
_[28]={_[46]}
_[27]={_[45]}
_[26]={_[44]}
_[25]={_[43]}
_[24]={_[42]}
_[23]={_[41]}
_[22]={_[40]}
_[21]={_[39]}
_[20]={_[38]}
_[19]={"return"}
_[18]={"text",_[37]}
_[17]={"text",_[36]}
_[16]={"text",_[35]}
_[15]={"text",_[34]}
_[14]={"text",_[33]}
_[13]={"text",_[32]}
_[12]={"text",_[31]}
_[11]={"text",_[30]}
_[10]={"text",_[29]}
_[9]={"text",_[28]}
_[8]={"text",_[27]}
_[7]={"text",_[26]}
_[6]={"text",_[25]}
_[5]={"text",_[24]}
_[4]={"text",_[23]}
_[3]={"text",_[22]}
_[2]={"text",_[21]}
_[1]={"text",_[20]}
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15],_[16],_[17],_[18],_[19]}
--[[
{ "text", { {
tags = {},
text = "0"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "2"
} } }
{ "text", { {
tags = {},
text = "3"
} } }
{ "text", { {
tags = {},
text = "4"
} } }
{ "text", { {
tags = {},
text = "5"
} } }
{ "text", { {
tags = {},
text = "6"
} } }
{ "text", { {
tags = {},
text = "7"
} } }
{ "text", { {
tags = {},
text = "8"
} } }
{ "text", { {
tags = {},
text = "9"
} } }
{ "text", { {
tags = {},
text = "10"
} } }
{ "text", { {
tags = {},
text = "return in loop:"
} } }
{ "text", { {
tags = {},
text = "0"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "2"
} } }
{ "text", { {
tags = {},
text = "3"
} } }
{ "text", { {
tags = {},
text = "4"
} } }
{ "text", { {
tags = {},
text = "5"
} } }
{ "return" }
]]--