1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-28 09:09: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. 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. * `>`: 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} > {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) (Conditionnaly executing a line)
@ -199,7 +217,7 @@ $ f(x::string)
~ f("hello") ~ 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_) (binary operator names: _op_)
@ -311,7 +329,7 @@ this is some text.
And this is more text, in a different event. 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) (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: From lowest to highest priority:
``` ```
_;_ _;_ _;
_:=_ _+=_ _-=_ _//=_ _/=_ _*=_ _%=_ _^=_ _:=_ _+=_ _-=_ _//=_ _/=_ _*=_ _%=_ _^=_
_,_ _,_
_|_ _&_ _~_ _#_ _|_ _&_ _~?_ _~_ _#_
_!=_ _==_ _>=_ _<=_ _<_ _>_ _!=_ _==_ _>=_ _<=_ _<_ _>_
_+_ _-_ _+_ _-_
_*_ _//_ _/_ _%_ _*_ _//_ _/_ _%_
@ -861,6 +879,10 @@ This only works on strings:
`a | b`: or operator, lazy `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 ##### Functions and function references
`fn(args)`: call the function, checkpoint or function reference with the given arguments. `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`: 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`: 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). `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", type = "nil",
value = 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 -- tag
elseif exp.type == "#" then elseif exp.type == "#" then
local right, righte = eval(state, exp.right) local right, righte = eval(state, exp.right)

View file

@ -30,40 +30,56 @@ run_line = function(state, line)
if v then return v end if v then return v end
end 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 elseif line.type == "choice" then
local v, e = events:make_space_for(state, "choice") 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 if not v then return v, ("%s; in automatic event flush at %s"):format(e, line.source) end
v, e = eval(state, line.text) v, e = eval(state, line.text)
if not v then return v, ("%s; at %s"):format(e, line.source) end 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 -- convert text events to choices
if v.type == "event buffer" then for _, item in ipairs(l) do
local current_tags = tags:current(state) if item.type == "event buffer" then
local choice_block_state = { tags = current_tags, block = line.child } local current_tags = tags:current(state)
local final_buffer = {} local choice_block_state = { tags = current_tags, block = line.child }
for _, event in ipairs(v.value) do local final_buffer = {}
if event.type == "text" then for _, event in ipairs(item.value) do
-- create new choice block if needed if event.type == "text" then
local last_choice_block = final_buffer[#final_buffer] -- create new choice block if needed
if not last_choice_block or last_choice_block.type ~= "choice" then local last_choice_block = final_buffer[#final_buffer]
last_choice_block = { type = "choice", value = {} } if not last_choice_block or last_choice_block.type ~= "choice" then
table.insert(final_buffer, last_choice_block) 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 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 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 end
v, e = events:write_buffer(state, final_buffer)
if not v then return v, ("%s; at %s"):format(e, line.source) end
end end
elseif line.type == "tag" then elseif line.type == "tag" then
local v, e = eval(state, line.expression) 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 if not v then return v, ("%s; in automatic event flush at %s"):format(e, line.source) end
v, e = eval(state, line.text) v, e = eval(state, line.text)
if not v then return v, ("%s; at %s"):format(e, line.source) end if not v then return v, ("%s; at %s"):format(e, line.source) end
if v.type == "event buffer" then local l = v.type == "list" and v.value or { v }
v, e = events:write_buffer(state, v.value) for _, item in ipairs(l) do
if not v then return v, ("%s; at %s"):format(e, line.source) end 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 end
elseif line.type == "flush_events" then elseif line.type == "flush_events" then
local v, e = events:flush(state) 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. 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: 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) 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 # color:red
a a
translate to translate to something like
~ tag.push(color:red) ~ tag.push(color:red)
a 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: 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 TODO: redisign a static type checking system
If we want to go full gradual typing, it would help to: 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: -- operators not included here and why:
-- * assignment operators (:=, +=, -=, //=, /=, *=, %=, ^=): handled with its own syntax (function assignment) -- * 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 -- * 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 -- * # operator: need to set tag state _before_ evaluating the left arg
-- prefix unop -- prefix unop

View file

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

View file

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

View file

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