mirror of
https://github.com/irmen/prog8.git
synced 2025-01-09 13:31:23 +00:00
unit test for defer, describe defer and if expression in docs
This commit is contained in:
parent
6da1f7eb4c
commit
326eab3dd1
@ -9,6 +9,7 @@ import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
@ -686,5 +687,45 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("defer syntactic sugaring") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
void test()
|
||||
}
|
||||
|
||||
sub test() -> uword {
|
||||
defer {
|
||||
cx16.r0++
|
||||
cx16.r1++
|
||||
}
|
||||
|
||||
if cx16.r0==0
|
||||
return cx16.r0+cx16.r1
|
||||
defer cx16.r2++
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), optimize=true, src, writeAssembly=true)!!
|
||||
val main = result.codegenAst!!.allBlocks().single {it.name=="p8b_main"}
|
||||
val sub = main.children[1] as PtSub
|
||||
sub.scopedName shouldBe "p8b_main.p8s_test"
|
||||
|
||||
// check the desugaring of the defer statements
|
||||
(sub.children[0] as PtVariable).name shouldBe "p8v_prog8_defers_mask"
|
||||
val ifelse = sub.children[3] as PtIfElse
|
||||
val ifscope = ifelse.ifScope.children[0] as PtNodeGroup
|
||||
val ifscope_push = ifscope.children[0] as PtFunctionCall
|
||||
val ifscope_defer = ifscope.children[1] as PtFunctionCall
|
||||
val ifscope_return = ifscope.children[2] as PtReturn
|
||||
ifscope_defer.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers"
|
||||
ifscope_push.name shouldBe "sys.pushw"
|
||||
(ifscope_return.value as PtFunctionCall).name shouldBe "sys.popw"
|
||||
val ending = sub.children[5] as PtFunctionCall
|
||||
ending.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers"
|
||||
sub.children[6] shouldBe instanceOf<PtReturn>()
|
||||
val handler = sub.children[7] as PtSub
|
||||
handler.name shouldBe "p8s_prog8_invoke_defers"
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -18,6 +18,7 @@ The language
|
||||
- Identifiers and string literals can contain non-ASCII characters so for example ``knäckebröd`` and ``見せしめ`` are valid identifiers.
|
||||
- There's usually a single statement per line. There is no statement separator.
|
||||
- Semicolon ``;`` is used to start a line comment. Multi-line comments are also possible by enclosing it all in ``/*`` and ``*/``.
|
||||
- Ternary operator ``x ? value1 : value2`` is available in the form of an *if-expression*: ``if x value1 else value2``
|
||||
|
||||
|
||||
No linker
|
||||
|
@ -551,8 +551,8 @@ Only simple statements are allowed to be inside an unroll loop (assignments, fun
|
||||
Conditional Execution
|
||||
---------------------
|
||||
|
||||
if statements
|
||||
^^^^^^^^^^^^^
|
||||
if statement
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Conditional execution means that the flow of execution changes based on certain conditions,
|
||||
rather than having fixed gotos or subroutine calls::
|
||||
@ -619,6 +619,25 @@ So ``if_cc goto target`` will directly translate into the single CPU instruction
|
||||
Maybe in the future this will be a separate nested scope, but for now, that is
|
||||
only possible when defining a subroutine.
|
||||
|
||||
|
||||
if expression
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
You can also use if..else as an *expression* instead of a statement. This expression selects one of two
|
||||
different values depending of the condition. Sometimes it may be more legible if you surround the condition expression with parentheses.
|
||||
An example, to select the number of cards to use depending on what game is played::
|
||||
|
||||
ubyte numcards = if game_is_piquet 32 else 52
|
||||
|
||||
; it's more verbose with an if statement:
|
||||
ubyte numcards
|
||||
if game_is_piquet
|
||||
numcards = 32
|
||||
else
|
||||
numcards = 52
|
||||
|
||||
|
||||
|
||||
when statement ('jump table')
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -798,6 +817,48 @@ Otherwise the compiler will issue a warning about discarding a result value.
|
||||
then undefined because the variables will get overwritten.
|
||||
|
||||
|
||||
Deferred ("cleanup") code
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Usually when a subroutine exits, it has to clean up things that it worked on. For example, it has to close
|
||||
a file that it opened before to read data from, or it has to free a piece of memory that it allocated via
|
||||
a dynamic memory allocation library, etc.
|
||||
Every spot where the subroutine exits (return statement, jump, or the end of the routine) you have to take care
|
||||
of doing the cleanups required. This can get tedious, and the cleanup code is separated from the place where
|
||||
the resource allocation was done at the start.
|
||||
|
||||
To help make this easier and less error prone, you can ``defer`` code to be executed automatically,
|
||||
immediately before any moment the subroutine exits. So for example to make sure a file is closed
|
||||
regardless of what happens later in the routine, you can write something along these lines::
|
||||
|
||||
sub example() -> bool {
|
||||
ubyte file = open_file()
|
||||
defer close_file(file) ; "close it when we exit from here"
|
||||
|
||||
uword memory = allocate(1000)
|
||||
if memory==0
|
||||
return false
|
||||
defer deallocate(memory) ; "deallocate when we exit from here"
|
||||
|
||||
process(file, memory)
|
||||
return true
|
||||
}
|
||||
|
||||
In this example, the two deferred statements are not immediately executed. Instead, they are executed when the
|
||||
subroutine exits at any point. So for example the ``return false`` after the memory check will automatically also close
|
||||
the file that was opened earlier because the close_file() call was scheduled there.
|
||||
At the bottom when the ``return true`` appears, *both* deferred cleanup calls are executed: first the deallocation of
|
||||
the memory, and then the file close. As you can see this saves you from duplicating the cleanup logic,
|
||||
and the logic is declared very close to the spot where the allocation of the resource happens, so it's easier to read and understand.
|
||||
|
||||
It's possible to write a defer for a block of statements, but the advice is to keep such cleanup code as simple and short as possible.
|
||||
|
||||
.. caution::
|
||||
Defers only work for subroutines that are written as regular Prog8 code.
|
||||
If a piece of inlined assembly somehow causes the routine to exit, the compiler cannot detect this.
|
||||
Defers will not be handled in such cases.
|
||||
|
||||
|
||||
Library routines and builtin functions
|
||||
--------------------------------------
|
||||
|
||||
|
@ -648,6 +648,10 @@ address of: ``&``
|
||||
This operator can also be used as a prefix to a variable's data type keyword to indicate that
|
||||
it is a memory mapped variable (for instance: ``&ubyte screencolor = $d021``)
|
||||
|
||||
ternary:
|
||||
Prog8 doesn't have a ternary operator to choose one of two values (``x? y : z`` in many other languages)
|
||||
instead it provides this feature in the form of an *if expression*. See below under "Conditional Execution".
|
||||
|
||||
precedence grouping in expressions, or subroutine parameter list: ``(`` *expression* ``)``
|
||||
Parentheses are used to group parts of an expression to change the order of evaluation.
|
||||
(the subexpression inside the parentheses will be evaluated first):
|
||||
@ -945,8 +949,8 @@ If you jump to an address variable (uword), it is doing an 'indirect' jump: the
|
||||
to the address that's currently in the variable.
|
||||
|
||||
|
||||
if statements
|
||||
^^^^^^^^^^^^^
|
||||
if statement
|
||||
^^^^^^^^^^^^
|
||||
|
||||
With the 'if' / 'else' statement you can execute code depending on the value of a condition::
|
||||
|
||||
@ -991,6 +995,16 @@ It can also be one of the four aliases that are easier to read: ``if_z``, ``if_n
|
||||
This is not always the case after a function call or other operations!
|
||||
If in doubt, check the generated assembly code!
|
||||
|
||||
if expression
|
||||
^^^^^^^^^^^^^
|
||||
Similar to the if statement, but this time selects one of two possible values as the outcome of the expression,
|
||||
depending on the condition. You write it as ``if <condition> <value1> else <value2>`` and it can be
|
||||
used anywhere an expression is used to assign or pass a value.
|
||||
The first value will be used if the condition is true, otherwise the second value is used.
|
||||
Sometimes it may be more legible if you surround the condition expression with parentheses so it is better
|
||||
separated visually from the first value following it.
|
||||
You must always provide two alternatives to choose from, and they can only be values (expressions).
|
||||
|
||||
|
||||
when statement ('jump table')
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -1018,3 +1032,24 @@ case you have to use { } to enclose them::
|
||||
else -> txt.print("don't know")
|
||||
}
|
||||
|
||||
|
||||
Deferred code ("cleanups")
|
||||
--------------------------
|
||||
|
||||
The ``defer`` keyword can be used to schedule a statement (or block of statements) to be executed
|
||||
just before exiting of the current subroutine. That can be via a return statement or a jump to somewhere else,
|
||||
or just the normal ending of the subroutine. This is often useful to "not forget" to clean up stuff,
|
||||
and if the subroutine has multiple ways or places where it can exit, it saves you from repeating
|
||||
the cleanup code at every exit spot. Multiple defers can be scheduled in a single subroutine (up to a maximum of 8).
|
||||
They are handled in reversed order. Return values are evaluated before any deferred code is executed.
|
||||
You write defers like so::
|
||||
|
||||
defer diskio.f_close()
|
||||
|
||||
; or multiple statements:
|
||||
|
||||
defer {
|
||||
diskio.f_close()
|
||||
memory.deallocate()
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,6 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- unit test for defer
|
||||
- describe defer in the manual
|
||||
|
||||
- unit test for ifexpression
|
||||
- describe ifexpression in the manual
|
||||
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user