diff --git a/doc/source/language/variables.rst b/doc/source/language/variables.rst index 7193add145..feb99891ef 100644 --- a/doc/source/language/variables.rst +++ b/doc/source/language/variables.rst @@ -92,6 +92,18 @@ Any variable declared with ``DECLARE``, ``DECLARE LOCAL``, or ``LOCAL`` will only exist inside the code block section it was created in. After that code block is finished, the variable will no longer exist. +It is also possible to declare multiple variables in a single ``DECLARE`` statement, +separated by commas, as shown below:: + + // These all do the exact same thing - make local variables: + DECLARE A IS 5, B TO 1, C TO "O". + LOCAL A IS 5, B TO 1, C TO "O". + DECLARE LOCAL A IS 5, B TO 1, C TO "O". + + // These do the exact same thing - make global variables: + GLOBAL A IS 5, B TO 1, C TO "O". + DECLARE GLOBAL A IS 5, B TO 1, C TO "O". + See Scoping: :::::::::::: @@ -268,6 +280,11 @@ This follows the :ref:`scoping rules explained below `. If the variable can be found in the current local scope, or any scope higher up, then it won't be created and instead the existing one will be used. +It is also possible to set the values of multiple variables in a single ``SET`` statement +by separating the assignments with commas, as shown below:: + + SET X TO 1, Y TO 5, S TO "abc". + .. _unset: ``UNSET`` diff --git a/kerboscript_tests/declaration/multi_declaration_test.ks b/kerboscript_tests/declaration/multi_declaration_test.ks new file mode 100644 index 0000000000..fc89343b99 --- /dev/null +++ b/kerboscript_tests/declaration/multi_declaration_test.ks @@ -0,0 +1,30 @@ +// Tests comma-separated declarations of variables + +print "Testing with 'SET'". +set a to 1, b to 2, c to "a". + +print " 1. The first variable has the correct value: " + (a = 1). +print " 2. The second variable has the correct value: " + (b = 2). +print " 3. The third variable has the correct value: " + (c = "a"). + +print "Testing with 'GLOBAL'". +global d is 5, e to 2. + +print " 1. The first variable has the correct value: " + (d = 5). +print " 2. The second variable has the correct value: " + (e = 2). + +print "Testing with 'DECLARE'". + +declare f is 12, g to 1. + +print " 1. The first variable has the correct value: " + (f = 12). +print " 2. The second variable has the correct value: " + (g = 1). + + +print "Testing with 'DECLARE GLOBAL'". + +declare h is 7, i to 15, j to 3. + +print " 1. The first variable has the correct value: " + (h = 7). +print " 2. The second variable has the correct value: " + (i = 15). +print " 3. The thrid variable has the correct value: " + (j = 3). diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index 4ad2e7012d..b938a39f21 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -2175,7 +2175,11 @@ private ParseNode DepthFirstLeftSearch(ParseNode node, TokenType tokType) private void VisitSetStatement(ParseNode node) { NodeStartHousekeeping(node); - ProcessSetOperation(node.Nodes[1], node.Nodes[3]); + + for (int i = 1; i < node.Nodes.Count; i += 4) + { + ProcessSetOperation(node.Nodes[i], node.Nodes[i + 2]); + } } /// @@ -2687,8 +2691,10 @@ private void VisitDeclareStatement(ParseNode node) // DECLARE [GLOBAL|LOCAL] identifier TO expr. if (lastSubNode.Token.Type == TokenType.declare_identifier_clause) { - VisitNode(lastSubNode.Nodes[2]); - AddOpcode(CreateAppropriateStoreCode(whereToStore, true, "$" + GetIdentifierText(lastSubNode.Nodes[0]))); + for (int i = 0; i < lastSubNode.Nodes.Count; i += 4) { + VisitNode(lastSubNode.Nodes[i + 2]); + AddOpcode(CreateAppropriateStoreCode(whereToStore, true, "$" + GetIdentifierText(lastSubNode.Nodes[i]))); + } } // If the declare statement is of the form: diff --git a/src/kOS.Safe/Compilation/KS/Parser.cs b/src/kOS.Safe/Compilation/KS/Parser.cs index 2dd1edfb98..229133482f 100644 --- a/src/kOS.Safe/Compilation/KS/Parser.cs +++ b/src/kOS.Safe/Compilation/KS/Parser.cs @@ -453,6 +453,39 @@ private void Parseset_stmt(ParseNode parent) // NonTerminalSymbol: set_stmt // Concat Rule Parseexpr(node); // NonTerminal Rule: expr + // Concat Rule + tok = scanner.LookAhead(TokenType.COMMA); // ZeroOrMore Rule + while (tok.Type == TokenType.COMMA) + { + + // Concat Rule + tok = scanner.Scan(TokenType.COMMA); // Terminal Rule: COMMA + n = node.CreateNode(tok, tok.ToString() ); + node.Token.UpdateRange(tok); + node.Nodes.Add(n); + if (tok.Type != TokenType.COMMA) { + tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.COMMA.ToString(), 0x1001, tok)); + return; + } + + // Concat Rule + Parsevaridentifier(node); // NonTerminal Rule: varidentifier + + // Concat Rule + tok = scanner.Scan(TokenType.TO); // Terminal Rule: TO + n = node.CreateNode(tok, tok.ToString() ); + node.Token.UpdateRange(tok); + node.Nodes.Add(n); + if (tok.Type != TokenType.TO) { + tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.TO.ToString(), 0x1001, tok)); + return; + } + + // Concat Rule + Parseexpr(node); // NonTerminal Rule: expr + tok = scanner.LookAhead(TokenType.COMMA); // ZeroOrMore Rule + } + // Concat Rule tok = scanner.Scan(TokenType.EOI); // Terminal Rule: EOI n = node.CreateNode(tok, tok.ToString() ); @@ -1332,6 +1365,65 @@ private void Parsedeclare_identifier_clause(ParseNode parent) // NonTerminalSymb // Concat Rule Parseexpr(node); // NonTerminal Rule: expr + // Concat Rule + tok = scanner.LookAhead(TokenType.COMMA); // ZeroOrMore Rule + while (tok.Type == TokenType.COMMA) + { + + // Concat Rule + tok = scanner.Scan(TokenType.COMMA); // Terminal Rule: COMMA + n = node.CreateNode(tok, tok.ToString() ); + node.Token.UpdateRange(tok); + node.Nodes.Add(n); + if (tok.Type != TokenType.COMMA) { + tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.COMMA.ToString(), 0x1001, tok)); + return; + } + + // Concat Rule + tok = scanner.Scan(TokenType.IDENTIFIER); // Terminal Rule: IDENTIFIER + n = node.CreateNode(tok, tok.ToString() ); + node.Token.UpdateRange(tok); + node.Nodes.Add(n); + if (tok.Type != TokenType.IDENTIFIER) { + tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.IDENTIFIER.ToString(), 0x1001, tok)); + return; + } + + // Concat Rule + tok = scanner.LookAhead(TokenType.TO, TokenType.IS); // Choice Rule + switch (tok.Type) + { // Choice Rule + case TokenType.TO: + tok = scanner.Scan(TokenType.TO); // Terminal Rule: TO + n = node.CreateNode(tok, tok.ToString() ); + node.Token.UpdateRange(tok); + node.Nodes.Add(n); + if (tok.Type != TokenType.TO) { + tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.TO.ToString(), 0x1001, tok)); + return; + } + break; + case TokenType.IS: + tok = scanner.Scan(TokenType.IS); // Terminal Rule: IS + n = node.CreateNode(tok, tok.ToString() ); + node.Token.UpdateRange(tok); + node.Nodes.Add(n); + if (tok.Type != TokenType.IS) { + tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.IS.ToString(), 0x1001, tok)); + return; + } + break; + default: + tree.Errors.Add(new ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected TO or IS.", 0x0002, tok)); + break; + } // Choice Rule + + // Concat Rule + Parseexpr(node); // NonTerminal Rule: expr + tok = scanner.LookAhead(TokenType.COMMA); // ZeroOrMore Rule + } + // Concat Rule tok = scanner.Scan(TokenType.EOI); // Terminal Rule: EOI n = node.CreateNode(tok, tok.ToString() ); diff --git a/src/kOS.Safe/Compilation/KS/kRISC.tpg b/src/kOS.Safe/Compilation/KS/kRISC.tpg index ec3d0c752e..c646ec3a95 100644 --- a/src/kOS.Safe/Compilation/KS/kRISC.tpg +++ b/src/kOS.Safe/Compilation/KS/kRISC.tpg @@ -153,7 +153,7 @@ directive -> lazyglobal_directive; // Add to this list later if we ma // ------------ statements -------------------- empty_stmt -> EOI; -set_stmt -> SET varidentifier TO expr EOI; +set_stmt -> SET varidentifier TO expr (COMMA varidentifier TO expr)* EOI; if_stmt -> IF expr instruction EOI? (ELSE instruction EOI?)?; until_stmt -> UNTIL expr instruction EOI?; fromloop_stmt -> FROM instruction_block UNTIL expr STEP instruction_block DO instruction EOI?; @@ -172,7 +172,7 @@ remove_stmt -> REMOVE expr EOI; log_stmt -> LOG expr TO expr EOI; break_stmt -> BREAK EOI; preserve_stmt -> PRESERVE EOI; -declare_identifier_clause -> IDENTIFIER (TO|IS) expr EOI; +declare_identifier_clause -> IDENTIFIER (TO|IS) expr (COMMA IDENTIFIER (TO|IS) expr)* EOI; declare_parameter_clause -> PARAMETER IDENTIFIER ((TO|IS) expr)? (COMMA IDENTIFIER ((TO|IS) expr)?)* EOI; declare_function_clause -> FUNCTION IDENTIFIER instruction_block EOI?; declare_lock_clause -> LOCK IDENTIFIER TO expr EOI;