* algorithm, two implementations that act the same, same type etc
@ 2021-02-14 19:35 Mehdi Saada
2021-02-15 11:07 ` AdaMagica
2021-02-15 13:02 ` Niklas Holsti
0 siblings, 2 replies; 9+ messages in thread
From: Mehdi Saada @ 2021-02-14 19:35 UTC (permalink / raw)
Hi,
I have an issue with an algorithm - self-imposed "homework" I guess, the first one is mine, the wrong, the second is the "right" one, that works.
I can't figure out why they would logically be any different.
basically instead of using a stack on access values, I stack the values themselves. the rest is the same.
except this function ALL the rest of the project is the same. they are in related package so from the first I call the second to ensure the result is right, and it shows its not.
could someone throw an eye and tell if it seems identical or that something gross escaped my sight ?
45+78*56-(5-(8+5)-7);
gives (really) : 4428 and mine gives 4413
11-0/5+88/5;
it should be: 28 and mine gives 11
my function:
function Calcul ( V : in T_Vect_Token) return Integer is
package ppile is new P_Pile_4(T_Elem => Integer,
Max_Pile => 80);
use ppile;
Pile_operandes : ppile.T_Pile(40);
op1_calcul, op2_calcul: Integer;
begin
for I in V'Range loop
if V(I).all'Tag = T_Token_Operande'Tag then
ppile.Empiler(Pile_operandes,T_Token_Operande(v(I).all).Get_Elem);
elsif V(I).all'Tag = T_Token_Operateur'Tag then
Depiler(Pile_operandes,op2_calcul);
Depiler(Pile_operandes,op1_calcul);
case T_Token_Operateur(v(I).all).L_Operateur is
when '*' => Empiler(Pile_operandes,op1_calcul * op2_calcul);
when '-' => Empiler(Pile_operandes,op1_calcul - op2_calcul);
when '+' => Empiler(Pile_operandes,op1_calcul + op2_calcul);
when '/' => Empiler(Pile_operandes,op1_calcul / op2_calcul);
end case;
else raise P_Expression.Exc_Erreur;
end if;
end loop;
Depiler(Une_Pile => Pile_operandes,
Elem => op2_calcul);
if ppile.Pile_Vide(Pile_operandes) then Put_Line("BIEN !"); end if;
Put_Line("vrai calcul : " & P_Expression.Calcul(V)'Image);
Put_Line("faux calcul: "); return op1_calcul;
exception
when E: others => Put_Line(Exception_Information(E)); return 0;
end Calcul;
the good function:
function Calcul ( V : in T_Vect_Token) return Integer is
Pile : P_Pile_Token.T_Pile(80);
Res : Integer;
Token1,
Token2 : T_Ptr_Token ;
Ptr_Token : T_Ptr_Token ;
begin
for I in V'range loop
Ptr_Token := V(I);
if Ptr_Token.all'Tag = T_Token_Operande'Tag
then
Empiler(Pile,Ptr_Token);
elsif
Ptr_Token.all'Tag = T_Token_Operateur'Tag
then
Depiler(Pile,Token2);
Depiler(Pile,Token1);
case Get_Elem(T_Token_Operateur(Ptr_Token.all)) is
when'+' => Res := Get_Elem (T_Token_Operande(Token1.all)) +
Get_Elem (T_Token_Operande(Token2.all));
when'-' => Res := Get_Elem (T_Token_Operande(Token1.all))-
Get_Elem (T_Token_Operande(Token2.all));
when'*' => Res := Get_Elem (T_Token_Operande(Token1.all)) *
Get_Elem (T_Token_Operande(Token2.all));
when'/' => Res := Get_Elem (T_Token_Operande(Token1.all)) /
Get_Elem (T_Token_Operande(Token2.all));
end case;
Empiler(Pile,Set_Ptr(Res));
else
Ada.Text_Io.Put_Line("TOKEN intru ");
raise Exc_Erreur;
end if;
end loop;
Depiler(Pile,Ptr_Token);
if Pile_Vide(Pile)
then
return Get_Elem(T_Token_Operande(Ptr_Token.all));
else
Ada.Text_Io.Put_Line("Pile non vide ");
raise Exc_Erreur;
end if;
exception
when others => Ada.Text_Io.Put_Line ("Erreur CALCUL");
raise Exc_Erreur;
end Calcul;
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: algorithm, two implementations that act the same, same type etc
2021-02-14 19:35 algorithm, two implementations that act the same, same type etc Mehdi Saada
@ 2021-02-15 11:07 ` AdaMagica
2021-02-15 13:02 ` Niklas Holsti
1 sibling, 0 replies; 9+ messages in thread
From: AdaMagica @ 2021-02-15 11:07 UTC (permalink / raw)
You've got a nerve...
Unreadable code fragment, uncompilable, terrible formatting, ...
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: algorithm, two implementations that act the same, same type etc
2021-02-14 19:35 algorithm, two implementations that act the same, same type etc Mehdi Saada
2021-02-15 11:07 ` AdaMagica
@ 2021-02-15 13:02 ` Niklas Holsti
2021-02-15 16:34 ` Mehdi Saada
1 sibling, 1 reply; 9+ messages in thread
From: Niklas Holsti @ 2021-02-15 13:02 UTC (permalink / raw)
On 2021-02-14 21:35, Mehdi Saada wrote:
> Hi,
> I have an issue with an algorithm - self-imposed "homework" I guess,
> the first one is mine, the wrong, the second is the "right" one, that
> works.
>
> I can't figure out why they would logically be any different.
>
> basically instead of using a stack on access values, I stack the
> values themselves. the rest is the same.
>
> except this function ALL the rest of the project is the same. they
> are in related package so from the first I call the second to ensure
> the result is right, and it shows its not.
>
> could someone throw an eye and tell if it seems identical or that
> something gross escaped my sight ?
First you should have told us what the function is supposed to do. I
would also advise you to write that descriptions as comments in the
code, BEFORE you start coding, so that (a) you know, while you are
writing the code, what the code is meant to do; and (b) someone reading
the code later has a chance of understanding the intent.
Looking first at what you call the "good" code, it seems to be given a
reverse-Polish (post-fix) integer expression, as a vector of objects,
each of which can be either an operand (an integer) or an operator (+,
-, *, /), and the purpose of the function is to evaluate the expression
and return the resulting integer value.
The evaluation function uses a stack of operands and results, in the
normal way:
traverse the post-fix expression items from first to last
if the item is an operand, push its value
if the item is an operation, pop the top two stack
elements, apply the operation, and push the result.
pop and return the (only) stack element.
> my function:
...
> Depiler(Une_Pile => Pile_operandes,
> Elem => op2_calcul);
...> return op1_calcul;
Have a good look at those two statements. You will see a discrepancy.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: algorithm, two implementations that act the same, same type etc
2021-02-15 13:02 ` Niklas Holsti
@ 2021-02-15 16:34 ` Mehdi Saada
2021-02-15 19:15 ` Niklas Holsti
0 siblings, 1 reply; 9+ messages in thread
From: Mehdi Saada @ 2021-02-15 16:34 UTC (permalink / raw)
I would not post code if I wasn't thinking I know of its purpose.
I just couldn't spot the one letter wrong I'm sure you must remember how as a student lines tended to "blurr"...
"idiot ! Look closely at the f%@# Depiler parts" or "reformat your damn message" or "look better at the details of polish inverse notation" would have sufficed. Being spiteful is a choice...
Thanks anyway.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: algorithm, two implementations that act the same, same type etc
2021-02-15 16:34 ` Mehdi Saada
@ 2021-02-15 19:15 ` Niklas Holsti
2021-02-17 10:50 ` Mehdi Saada
0 siblings, 1 reply; 9+ messages in thread
From: Niklas Holsti @ 2021-02-15 19:15 UTC (permalink / raw)
On 2021-02-15 18:34, Mehdi Saada wrote:
>
> I would not post code if I wasn't thinking I know of its purpose.
You may know, but we (the newsgroup) don't, and you asked us to read
your code and try to find your error. The error turned out to be a typo,
but it could have been a mismatch between what you wanted the code to
do, and how the code was designed. That is why I asked you to explain
the purpose. Also, some of the identifiers you used are French-derived,
and some of us may not French and find the identifiers obscure.
(Perhaps you can take my request as a compliment to you: that I did not
expect that you would make such a simple typo mistake, or would have
found such a mistake on your own.)
I find that writing a description, in prose comments, of a subprogram,
before I start coding the subprogram, forces me to think about it, and
reduces the chance of design errors in the algorithm. This is especially
true if there are corner cases like empty inputs, empty results, or
input checks that can fail.
> I just couldn't spot the one letter wrong I'm sure you must remember
> how as a student lines tended to "blurr"...
Sure.
Among the (language-independent) lessons I have learned from making many
mistakes of similar triviality are these:
1. Always document (with comments) the purpose (role) of every variable,
constant, parameter, etc. This is so that (a) you think about it, and
remember it, and (b) anyone reading the code later will understand the
intent.
2. Never use a variable for more than one purpose.
In this example, your basic error was trying to reuse the op<x>_calcul
variables for a different purpose. These variables were designed to hold
the two operands for an operation, and were so named, but you tried to
reuse one of them for a different purpose: extracting and passing the
final result. You should have declared a new variable, result: Integer,
for that different purpose.
Moreover, I suspect that a part of the error may have been a copy-paste
of a "Depiler" call without paying attention to editing the pasted copy.
So, more rules I have learned:
3. Treat every copy-paste of code with extreme suspicion. Avoid being
interrupted by anything before you have finished adjusting the pasted
copy to its new role. One method is to start by making the pasted copy
syntactically incorrect, for example by inserting !! before it, and only
removing the !! after you are sure that the pasted text is now adapted
for its new use.
In one project for some on-board SW for a satellite instrument that I
worked on a few decades ago, we found that a large portion -- I believe
something over one-half -- of the bugs found in unit test were caused by
such omitted or incomplete editing of copy-pasted code.
The problem is that unedited copy-pasted code often compiles without
error, and may look nice and ready, but does not work correctly in its
new context and role.
4. The "blurring" problem is reduced by adding a blank line between
statements (but not within a statement).
Finally, note that your design change -- to stack the values instead of
the references to the original reverse-Polish objects -- was valid and
worked, good for you.
I wish you better luck with your next program.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: algorithm, two implementations that act the same, same type etc
2021-02-15 19:15 ` Niklas Holsti
@ 2021-02-17 10:50 ` Mehdi Saada
2021-02-17 12:14 ` Mehdi Saada
0 siblings, 1 reply; 9+ messages in thread
From: Mehdi Saada @ 2021-02-17 10:50 UTC (permalink / raw)
Thanks.
The tension subsided a little. I got weirdly emotional, sorry.
Your stories and teachings are enlightening, now I understand why some code would have more comments than actual code.
Makes sense if I consider that the language grammar itself appears as mostly useless commentary for C people.
I'll refactor stuff and you'll tell if that complies with your standard.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: algorithm, two implementations that act the same, same type etc
2021-02-17 10:50 ` Mehdi Saada
@ 2021-02-17 12:14 ` Mehdi Saada
2021-02-17 17:10 ` Niklas Holsti
0 siblings, 1 reply; 9+ messages in thread
From: Mehdi Saada @ 2021-02-17 12:14 UTC (permalink / raw)
>> I'll refactor stuff and you'll tell if that complies with your standard.
I hope that's better.
------------------
body part of "p_expression_a.a_moi")
function Computation (V: in T_Vect_Token) return integer is separate;
function Calcul(V: in T_Vect_Token) return Integer renames Computation;
----------------------
separate (P_Expression.A_Moi)
function Computation ( V : in T_Vect_Token) return Integer is
package P_stack is new P_Pile_4(T_Elem => Integer,
Max_Pile => 80);
-- call on stack package
subtype T_stack is P_stack.T_Pile;
subtype T_Token_Operand is T_Token_Operande;
subtype T_Token_Operator is T_Token_Operateur;
procedure Stack_Uppon (A_Stack : in out T_stack; Elem: Integer)
renames P_stack.Empiler;
procedure Pop (A_Stack: in out T_STACK; Elem: out Integer)
renames P_stack.Depiler;
function Is_Stack_Empty (A_Stack: T_stack) return Boolean
renames P_stack.Pile_Vide;
Exception_Wrong_Token : exception renames P_Expression.Exc_Erreur;
-- renamings for conveniance, avoids usage of "use"
Stack_for_Operands : T_stack(40);
Operand_1, Operand_2: Integer;
begin
-- Loop runs through the token vector, if it's an operand it's stacked,
-- if it's an operator the last two operands stacked are recovered and the computation made,
-- but in the inverse order of the recovered operands, or "/" and "-" would misbehave.
for I in V'Range loop
-- Tag comparisons to choose decision
if V(I).all'Tag = T_Token_Operand'Tag then
Stack_Uppon(Stack_for_operands,T_Token_Operand(v(I).all).Get_Elem);
-- Get the value to stack
elsif V(I).all'Tag = T_Token_Operateur'Tag then
Pop(Stack_for_operands,Operand_2);
Pop(Stack_for_operands,Operand_1);
-- decision over which operator to apply.
case T_Token_Operateur(v(I).all).L_Operateur is
when '*' => Stack_Uppon(Stack_for_operands,Operand_1 * Operand_2);
when '-' => Stack_Uppon(Stack_for_operands,Operand_1 - Operand_2);
when '+' => Stack_Uppon(Stack_for_operands,Operand_1 + Operand_2);
when '/' => Stack_Uppon(Stack_for_operands,Operand_1 / Operand_2);
end case;
else raise P_Expression.Exc_Erreur
with "wrong token inserted please review previous steps";
-- if any wrong token (parentheses, terminator) has still crept in
-- from the previous steps. Or if the vector is invalid and it has not been seen,
end if;
end loop;
declare
Result: INTEGER renames Operand_1;
-- clarity rules !
begin
Pop(A_Stack => Stack_for_Operands,
Elem => Result);
pragma Assert (Is_Stack_Empty(Stack_for_operands)
and (P_Expression.Calcul(V) = Result));
return Result;
end;
exception
when P_stack.Exc_Pile_Vide => Put_Line("Invalidity not recognized by previous subroutine");
return 0;
when E: others => Put_Line(Exception_Information(E)); return 0;
end Computation;
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: algorithm, two implementations that act the same, same type etc
2021-02-17 12:14 ` Mehdi Saada
@ 2021-02-17 17:10 ` Niklas Holsti
2021-02-17 21:13 ` Mehdi Saada
0 siblings, 1 reply; 9+ messages in thread
From: Niklas Holsti @ 2021-02-17 17:10 UTC (permalink / raw)
On 2021-02-17 14:14, Mehdi Saada wrote:
>>> I'll refactor stuff and you'll tell if that complies with your standard.
We won't know who's standard you mean if you don't quote something from
the person or name the person.
I'll assume it's me, so I'll review your code from my point of view. I
won't put in softening polite words everywhere, but don't be offended by
my criticism, please :-)
> I hope that's better.
> ------------------
> body part of "p_expression_a.a_moi")
> function Computation (V: in T_Vect_Token) return integer is separate;
> function Calcul(V: in T_Vect_Token) return Integer renames Computation;
The first thing a reader would ask themselves is "why that renaming"?
That would be worth a comment.
My practice is to describe the role and meaning of a subprogram in
connection with that subprogram's declaration, which you haven't done
here. Even a one-liner like "The result of evaluating the post-fix
expression V" would be illuminating.
> ----------------------
> separate (P_Expression.A_Moi)
> function Computation ( V : in T_Vect_Token) return Integer is
>
> package P_stack is new P_Pile_4(T_Elem => Integer,
> Max_Pile => 80);
>
> -- call on stack package
I don't see what the comment above brings to the table. I can see from
the code that something related to a stack package is being done; I
don't need a comment to tell me that. The comment should tell me
something that I cannot see directly from the Ada code. Perhaps that the
stack in question will hold operand and result values (which "T_Elem =>
Integer" does not tell me, because Integer is such a general type).
I also make a rule that comments should be written in normal prose
style, as full and grammatical sentences, with an initial capital letter
and a final period in each sentence.
> subtype T_stack is P_stack.T_Pile;
> subtype T_Token_Operand is T_Token_Operande;
> subtype T_Token_Operator is T_Token_Operateur;
> procedure Stack_Uppon (A_Stack : in out T_stack; Elem: Integer)
> renames P_stack.Empiler;
> procedure Pop (A_Stack: in out T_STACK; Elem: out Integer)
> renames P_stack.Depiler;
> function Is_Stack_Empty (A_Stack: T_stack) return Boolean
> renames P_stack.Pile_Vide;
> Exception_Wrong_Token : exception renames P_Expression.Exc_Erreur;
If those renames are only to translate French to English, you took my
comment about French too seriously. There's nothing bad with using other
languages for identifiers and comments -- I sometimes use Finnish -- but
if you then post the code in an English-language forum like
comp.lang.ada, you can expect some incomprehension if you don't explain
the names in your message, for example saying "Empiler" = "push" and
"Depiler" = "pop".
If you prefer French discussions, you can post on fr.comp.lang.ada.
<anecdote>
I once took part in an ESA project by a French company that was building
an ESA satellite based on an earlier French national (CNES) satellite.
All code and documentation for the earlier satellite was in French, but
the ESA satellite required English documentation, and also some changes
to the SW. So, for a while the French "method" for marking changes in
the SW requirements specification document was that all old text was in
French, all changes and new text in English -- even when changing only
part of a sentence.
And of course all acronyms came in two forms, like OTAN (French) = NATO
(English). This method somewhat limited the staff we could assign to the
project.
</anecdote>
> -- renamings for conveniance, avoids usage of "use"
The renames added 10 lines to the code, with possible errors in them,
replacing one line for "use", or a few cases of qualification by package
name. Not a win, IMO.
You could also have avoided "use" by saying "type T_Stack is new
P_Stack.T_Pile" (which would inherit all operations of T_Pile) instead
of using a subtype. Or you could say "use type T_Stack" or "use type
P_Stack.T_Pile" with the same effect.
My personal rule for "use" is to limit it to scopes that are so small
(few lines) that the "use" is always visible on the screen when I am
working on the code affected by it. And even so only when it really
makes the code significantly easier to read.
Note that avoiding "use <package>" is made much easier by hierarchical
packages: code in package Foo.Bar.Joe sees everything declared in
packages Foo and Foo.Bar without having to qualify them or saying "use
Foo" or "use Foo.Bar".
>
> Stack_for_Operands : T_stack(40);
> Operand_1, Operand_2: Integer;
Your identifiers are clear enough to almost avoid the need for comments
per object. But the penalty for that is that the identifiers are long,
especially the first one. I would probably have written:
Stack : T_Stack (40);
-- Holds operands and computation results.
(By the way, what is the "40" for here? To set the maximum stack size?
But isn't that given by "Max_Pile => 80" in the generic instantiation?)
> begin
>
> -- Loop runs through the token vector, if it's an operand it's stacked,
> -- if it's an operator the last two operands stacked are recovered and the computation made,
> -- but in the inverse order of the recovered operands, or "/" and "-" would misbehave.
>
> for I in V'Range loop
>
> -- Tag comparisons to choose decision
Some people write comments before the subject code, some people write
comments after the subject code. I do both, but for different purposes,
and I write the comment differently to separate the two cases.
When I want to write a comment that applies to a long piece of code, for
example several statements or declarations (as in the earlier renamings)
I put the comment before the code, surround it with blank lines, and end
it with a colon. I would replace the above comment with:
-- See if V(I) is an operand or an operator:
The comment does not have to say "Tag comparisons"; I can directly see
from the code that it is comparing tags. The phrase "to choose decision"
is redundant; any comparison is done to choose or decide something. The
important thing is what is _specific_ to these comparisons and
decisions, which here is to separate operands from operators.
The common example of a useless comment is:
I := I + 1;
-- Add one to I.
Don't emulate it.
When I want to write a comment that applies to a short piece of code,
such as a single declaration or statement, I put the comment after the
code and ensure that there are no blank lines between the code and the
comment. I usually add a null comment between the code and the comment,
to avoid the "blurring" effect:
Result : Integer;
--
-- The result of the computation.
> if V(I).all'Tag = T_Token_Operand'Tag then
My practice is to write a comment after every "then" and "else" to
explain the situation. Here I would have written:
if V(I).all'Tag = T_Token_Operand'Tag then
-- This is an operand, so we push its value on the stack:
> Stack_Uppon(Stack_for_operands,T_Token_Operand(v(I).all).Get_Elem);
The usual English name for pushing something on a stack is "Push", just
a note.
> -- Get the value to stack
If that comment applies to the "Stack_Uppon" statement, it should be
indented as much as that statement. And I would add a null comment
between the statement and the comment to show that the comment applies
to the preceding statement.
> elsif V(I).all'Tag = T_Token_Operateur'Tag then
And here I would write, after the "then":
-- This is an operator, so we apply it to the two top operands
-- from the stack, and replace the operands with the result:
> Pop(Stack_for_operands,Operand_2);
> Pop(Stack_for_operands,Operand_1);
>
> -- decision over which operator to apply.
>
> case T_Token_Operateur(v(I).all).L_Operateur is
> when '*' => Stack_Uppon(Stack_for_operands,Operand_1 * Operand_2);
> when '-' => Stack_Uppon(Stack_for_operands,Operand_1 - Operand_2);
> when '+' => Stack_Uppon(Stack_for_operands,Operand_1 + Operand_2);
> when '/' => Stack_Uppon(Stack_for_operands,Operand_1 / Operand_2);
> end case;
>
> else raise P_Expression.Exc_Erreur
> with "wrong token inserted please review previous steps";
>
> -- if any wrong token (parentheses, terminator) has still crept in
> -- from the previous steps. Or if the vector is invalid and it has not been seen,
A further rule: "else" should almost always be followed by a line break
and a comment to explain the case. So here I would write:
else
-- This is neither an operand nor an operator, which means
-- that the input V is in error.
raise ...
>
> end if;
> end loop;
>
> declare
> Result: INTEGER renames Operand_1;
Why use a renaming here? It buys you nothing; it probably won't even
save a few bits of memory. All it does is change the value of Operand_1
in a sneaky manner.
> -- clarity rules !
That comment is almost masterfully unclear, similar to "this statement
is a lie".
I give you credit for localizing the declaration to a declare block.
However, why were you not consistent and localized Operand_1 and
Operand_2 also?
Anyway, for a short subprogram like this, I think using declare blocks
is superfluous because the declarations are close to the code anyway.
> begin
> Pop(A_Stack => Stack_for_Operands,
> Elem => Result);
> pragma Assert (Is_Stack_Empty(Stack_for_operands)
> and (P_Expression.Calcul(V) = Result));
If you use a complex Boolean expression in an assertion, and the
assertion fails, you won't know which part of the Boolean expression was
False. Better to separate into two assertions, one for the stack being
empty, and one for checking the result.
> return Result;
> end;
> exception
> when P_stack.Exc_Pile_Vide => Put_Line("Invalidity not recognized by previous subroutine");
> return 0;
> when E: others => Put_Line(Exception_Information(E)); return 0;
> end Computation;
For exception handlers I usually follow the same rule as for "then" and
"else": ensure a line break after "=>" and follow it with a comment that
explains why this exception might happen (if it isn't evident). For example:
when P_Stack.Exc_Pile_Vide =>
-- The expression V is ill-formed; it tries to apply an operation
-- to a stack that is empty or contains only one operand, or the
-- entire expression is null and produces nothing.
In conclusion, if this were a real code review in a project, I would say
"please correct accordingly and submit for re-review".
No hurt feelings, I hope.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: algorithm, two implementations that act the same, same type etc
2021-02-17 17:10 ` Niklas Holsti
@ 2021-02-17 21:13 ` Mehdi Saada
0 siblings, 0 replies; 9+ messages in thread
From: Mehdi Saada @ 2021-02-17 21:13 UTC (permalink / raw)
I loved it, thank you Niklas.
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2021-02-17 21:13 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-14 19:35 algorithm, two implementations that act the same, same type etc Mehdi Saada
2021-02-15 11:07 ` AdaMagica
2021-02-15 13:02 ` Niklas Holsti
2021-02-15 16:34 ` Mehdi Saada
2021-02-15 19:15 ` Niklas Holsti
2021-02-17 10:50 ` Mehdi Saada
2021-02-17 12:14 ` Mehdi Saada
2021-02-17 17:10 ` Niklas Holsti
2021-02-17 21:13 ` Mehdi Saada
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox