Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #18 #2549

Merged
merged 1 commit into from
Jun 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions doc/source/language/flow.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,32 @@ Checks if the expression supplied returns true. If it does, ``IF`` executes the
// syntax error - ELSE without IF.
IF X > 10 { PRINT "Large". }. ELSE { PRINT "Small". }.

.. index:: CHOOSE
.. _choose:

CHOOSE (Ternary operator)
-------------------------

An expression that evalualtes to one of two choices depending on a
conditional check:

CHOOSE expression1 IF condition ELSE expression2

Note this is NOT a statement. This is an expression that can be embedded
inside other statements, like so:

SET X TO CHOOSE expression1 IF condition ELSE expression2.
PRINT CHOOSE "High" IF altitude > 20000 ELSE "Low".

The reason to use the ``CHOOSE`` operator instead of an
IF/ELSE statement is that IF/ELSE won't return a value, while
this does, and thus this can be embedded inside other expressions.

(This is similar to the ``?`` operator in languages like "C" and its
derivatives, except it puts the "true" choice first, then the
conditional check, then the "false" choice.)


.. index:: LOCK
.. _lock:

Expand Down
38 changes: 38 additions & 0 deletions kerboscript_tests/ternary.ks
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
print "THESE are tests of the ternary CHOOSE operator.".

Print "Basic case - Next line should print 'A':".
print choose "A" if true else "B".

Print "Basic case - Next line should print 'B':".
print choose "A" if false else "B".


print "Testing nested CHOOSEs. Next line should be 'ABCDE*****':".
set str to "".
for i in range(0,10) {
set str to str + (choose "A" if i = 0 else choose "B" if i = 1 else choose "C" if i = 2 else choose "D" if i = 3 else choose "E" if i = 4 else "*").

}
print str.


print "Weird case - nesting choose in the boolean.".
set x to true.
set y to false.
set a to 1.
print " Next 2 lines should be 'A':".
print " " + (choose "A" if (choose x if a=1 else y) else "B").
print " " + (choose "A" if choose x if a=1 else y else "B"). // same without parens should work.
print " Next 2 lines should be 'B':".
print " " + (choose "A" if (choose x if a=2 else y) else "B").
print " " + (choose "A" if choose x if a=2 else y else "B"). // same without parens should work.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
+
+print "Third nesting case - nesting choose in the first term.".
+print " Next two lines should be 'A'.".
+print " " + (choose (choose "A" if true else "B") if true else "C").
+print " " + (choose choose "A" if true else "B" if true else "C"). // same without parens should work.
+print " Next two lines should be 'B'.".
+print " " + (choose (choose "A" if false else "B") if true else "C").
+print " " + (choose choose "A" if false else "B" if true else "C"). // same without parens should work.
+print " Next four lines should be 'C'.".
+print " " + (choose (choose "A" if true else "B") if false else "C").
+print " " + (choose choose "A" if true else "B" if false else "C"). // same without parens should work.
+print " " + (choose (choose "A" if false else "B") if false else "C"). // result of inner test should affect result
+print " " + (choose choose "A" if false else "B" if false else "C"). // same without parens should work.
+
+

print "Complex case - selecting delegate with choose.".
set a to 1.
set del1 to choose { print "trueDel". } if a = 1 else { print "falseDel.". }.
set a to 2.
set del2 to choose { print "trueDel". } if a = 1 else { print "falseDel.". }.
print "Next line should say 'trueDel':".
del1:call().
print "Next line should say 'falseDel':".
del2:call().
31 changes: 31 additions & 0 deletions src/kOS.Safe/Compilation/KS/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ private void IterateUserFunctions(ParseNode node, Action<ParseNode> action)
case TokenType.COMPARATOR:
case TokenType.AND:
case TokenType.OR:
case TokenType.CHOOSE:
case TokenType.directive:
doChildren = false;
break;
Expand Down Expand Up @@ -1149,6 +1150,9 @@ private void VisitNode(ParseNode node)
case TokenType.expr:
VisitExpr(node);
break;
case TokenType.ternary_expr:
VisitTernary(node);
break;
case TokenType.or_expr:
case TokenType.and_expr:
VisitShortCircuitBoolean(node);
Expand Down Expand Up @@ -1352,6 +1356,33 @@ private void VisitExpr(ParseNode node)
}
}

private void VisitTernary(ParseNode node)
{
NodeStartHousekeeping(node);

// Syntax pattern is:
// [0] = keyword CHOOSE
// [1] = expression returned if true
// [2] = keyword IF
// [3] = expression with boolean value
// [4] = keyword ELSE
// [5] = expression returned if false

VisitNode(node.Nodes[3]); // eval the boolean clause, put on stack.

Opcode bypassTrue = AddOpcode(new OpcodeBranchIfFalse());

VisitNode(node.Nodes[1]); // expression if true.
Opcode bypassFalse = AddOpcode(new OpcodeBranchJump());

bypassTrue.DestinationLabel = GetNextLabel(false);

VisitNode(node.Nodes[5]); // expression if false.

bypassFalse.DestinationLabel = GetNextLabel(false);
addBranchDestination = true;
}

/// <summary>
/// Handles the short-circuit logic of boolean OR and boolean AND
/// chains. It is like VisitExpressionChain (see elsewhere) but
Expand Down
10 changes: 10 additions & 0 deletions src/kOS.Safe/Compilation/KS/ParseTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ internal object Eval(ParseTree tree, params object[] paramlist)
case TokenType.expr:
Value = Evalexpr(tree, paramlist);
break;
case TokenType.ternary_expr:
Value = Evalternary_expr(tree, paramlist);
break;
case TokenType.or_expr:
Value = Evalor_expr(tree, paramlist);
break;
Expand Down Expand Up @@ -711,6 +714,13 @@ protected virtual object Evalexpr(ParseTree tree, params object[] paramlist)
return null;
}

protected virtual object Evalternary_expr(ParseTree tree, params object[] paramlist)
{
foreach (var node in Nodes)
node.Eval(tree, paramlist);
return null;
}

protected virtual object Evalor_expr(ParseTree tree, params object[] paramlist)
{
foreach (var node in Nodes)
Expand Down
67 changes: 61 additions & 6 deletions src/kOS.Safe/Compilation/KS/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1749,8 +1749,9 @@ private void Parsereturn_stmt(ParseNode parent) // NonTerminalSymbol: return_stm
}

// Concat Rule
tok = scanner.LookAhead(TokenType.PLUSMINUS, TokenType.NOT, TokenType.DEFINED, TokenType.INTEGER, TokenType.DOUBLE, TokenType.TRUEFALSE, TokenType.IDENTIFIER, TokenType.FILEIDENT, TokenType.BRACKETOPEN, TokenType.STRING, TokenType.CURLYOPEN); // Option Rule
if (tok.Type == TokenType.PLUSMINUS
tok = scanner.LookAhead(TokenType.CHOOSE, TokenType.PLUSMINUS, TokenType.NOT, TokenType.DEFINED, TokenType.INTEGER, TokenType.DOUBLE, TokenType.TRUEFALSE, TokenType.IDENTIFIER, TokenType.FILEIDENT, TokenType.BRACKETOPEN, TokenType.STRING, TokenType.CURLYOPEN); // Option Rule
if (tok.Type == TokenType.CHOOSE
|| tok.Type == TokenType.PLUSMINUS
|| tok.Type == TokenType.NOT
|| tok.Type == TokenType.DEFINED
|| tok.Type == TokenType.INTEGER
Expand Down Expand Up @@ -2674,9 +2675,12 @@ private void Parseexpr(ParseNode parent) // NonTerminalSymbol: expr
ParseNode node = parent.CreateNode(scanner.GetToken(TokenType.expr), "expr");
parent.Nodes.Add(node);

tok = scanner.LookAhead(TokenType.PLUSMINUS, TokenType.NOT, TokenType.DEFINED, TokenType.INTEGER, TokenType.DOUBLE, TokenType.TRUEFALSE, TokenType.IDENTIFIER, TokenType.FILEIDENT, TokenType.BRACKETOPEN, TokenType.STRING, TokenType.CURLYOPEN); // Choice Rule
tok = scanner.LookAhead(TokenType.CHOOSE, TokenType.PLUSMINUS, TokenType.NOT, TokenType.DEFINED, TokenType.INTEGER, TokenType.DOUBLE, TokenType.TRUEFALSE, TokenType.IDENTIFIER, TokenType.FILEIDENT, TokenType.BRACKETOPEN, TokenType.STRING, TokenType.CURLYOPEN); // Choice Rule
switch (tok.Type)
{ // Choice Rule
case TokenType.CHOOSE:
Parseternary_expr(node); // NonTerminal Rule: ternary_expr
break;
case TokenType.PLUSMINUS:
case TokenType.NOT:
case TokenType.DEFINED:
Expand All @@ -2693,13 +2697,63 @@ private void Parseexpr(ParseNode parent) // NonTerminalSymbol: expr
Parseinstruction_block(node); // NonTerminal Rule: instruction_block
break;
default:
tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected PLUSMINUS, NOT, DEFINED, INTEGER, DOUBLE, TRUEFALSE, IDENTIFIER, FILEIDENT, BRACKETOPEN, STRING, or CURLYOPEN.", 0x0002, tok));
tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected CHOOSE, PLUSMINUS, NOT, DEFINED, INTEGER, DOUBLE, TRUEFALSE, IDENTIFIER, FILEIDENT, BRACKETOPEN, STRING, or CURLYOPEN.", 0x0002, tok));
break;
} // Choice Rule

parent.Token.UpdateRange(node.Token);
} // NonTerminalSymbol: expr

private void Parseternary_expr(ParseNode parent) // NonTerminalSymbol: ternary_expr
{
Token tok;
ParseNode n;
ParseNode node = parent.CreateNode(scanner.GetToken(TokenType.ternary_expr), "ternary_expr");
parent.Nodes.Add(node);


// Concat Rule
tok = scanner.Scan(TokenType.CHOOSE); // Terminal Rule: CHOOSE
n = node.CreateNode(tok, tok.ToString() );
node.Token.UpdateRange(tok);
node.Nodes.Add(n);
if (tok.Type != TokenType.CHOOSE) {
tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.CHOOSE.ToString(), 0x1001, tok));
return;
}

// Concat Rule
Parseexpr(node); // NonTerminal Rule: expr

// Concat Rule
tok = scanner.Scan(TokenType.IF); // Terminal Rule: IF
n = node.CreateNode(tok, tok.ToString() );
node.Token.UpdateRange(tok);
node.Nodes.Add(n);
if (tok.Type != TokenType.IF) {
tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.IF.ToString(), 0x1001, tok));
return;
}

// Concat Rule
Parseexpr(node); // NonTerminal Rule: expr

// Concat Rule
tok = scanner.Scan(TokenType.ELSE); // Terminal Rule: ELSE
n = node.CreateNode(tok, tok.ToString() );
node.Token.UpdateRange(tok);
node.Nodes.Add(n);
if (tok.Type != TokenType.ELSE) {
tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.ELSE.ToString(), 0x1001, tok));
return;
}

// Concat Rule
Parseexpr(node); // NonTerminal Rule: expr

parent.Token.UpdateRange(node.Token);
} // NonTerminalSymbol: ternary_expr

private void Parseor_expr(ParseNode parent) // NonTerminalSymbol: or_expr
{
Token tok;
Expand Down Expand Up @@ -3103,8 +3157,9 @@ private void Parsefunction_trailer(ParseNode parent) // NonTerminalSymbol: funct
}

// Concat Rule
tok = scanner.LookAhead(TokenType.PLUSMINUS, TokenType.NOT, TokenType.DEFINED, TokenType.INTEGER, TokenType.DOUBLE, TokenType.TRUEFALSE, TokenType.IDENTIFIER, TokenType.FILEIDENT, TokenType.BRACKETOPEN, TokenType.STRING, TokenType.CURLYOPEN); // Option Rule
if (tok.Type == TokenType.PLUSMINUS
tok = scanner.LookAhead(TokenType.CHOOSE, TokenType.PLUSMINUS, TokenType.NOT, TokenType.DEFINED, TokenType.INTEGER, TokenType.DOUBLE, TokenType.TRUEFALSE, TokenType.IDENTIFIER, TokenType.FILEIDENT, TokenType.BRACKETOPEN, TokenType.STRING, TokenType.CURLYOPEN); // Option Rule
if (tok.Type == TokenType.CHOOSE
|| tok.Type == TokenType.PLUSMINUS
|| tok.Type == TokenType.NOT
|| tok.Type == TokenType.DEFINED
|| tok.Type == TokenType.INTEGER
Expand Down
Loading