[wgsl-reader] Adding for loops
Implementing ParserImpl::for_stmt(). Also adding
for_stmt and body_stmt to ParserImpl::statement().
Bug: tint:138
Change-Id: Idc3f901648ca115f4d94b3614a940263ef841282
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/27261
Commit-Queue: Tomek Ponitka <tommek@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 423d670..34dcabb 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -884,6 +884,7 @@
"src/reader/wgsl/parser_impl_entry_point_decl_test.cc",
"src/reader/wgsl/parser_impl_equality_expression_test.cc",
"src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc",
+ "src/reader/wgsl/parser_impl_for_stmt_test.cc",
"src/reader/wgsl/parser_impl_function_decl_test.cc",
"src/reader/wgsl/parser_impl_function_header_test.cc",
"src/reader/wgsl/parser_impl_function_type_decl_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e735d3b..68707f4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -417,6 +417,7 @@
reader/wgsl/parser_impl_entry_point_decl_test.cc
reader/wgsl/parser_impl_equality_expression_test.cc
reader/wgsl/parser_impl_exclusive_or_expression_test.cc
+ reader/wgsl/parser_impl_for_stmt_test.cc
reader/wgsl/parser_impl_function_decl_test.cc
reader/wgsl/parser_impl_function_header_test.cc
reader/wgsl/parser_impl_function_type_decl_test.cc
diff --git a/src/reader/wgsl/lexer.cc b/src/reader/wgsl/lexer.cc
index f38d9b7..981ea69 100644
--- a/src/reader/wgsl/lexer.cc
+++ b/src/reader/wgsl/lexer.cc
@@ -513,6 +513,8 @@
return {Token::Type::kFalse, source, "false"};
if (str == "fn")
return {Token::Type::kFn, source, "fn"};
+ if (str == "for")
+ return {Token::Type::kFor, source, "for"};
if (str == "fragment")
return {Token::Type::kFragment, source, "fragment"};
if (str == "function")
@@ -610,8 +612,6 @@
return {Token::Type::kReservedKeyword, source, "f16"};
if (str == "f64")
return {Token::Type::kReservedKeyword, source, "f64"};
- if (str == "for")
- return {Token::Type::kReservedKeyword, source, "for"};
if (str == "i8")
return {Token::Type::kReservedKeyword, source, "i8"};
if (str == "i16")
diff --git a/src/reader/wgsl/lexer_test.cc b/src/reader/wgsl/lexer_test.cc
index d8ed5ee..c2edd00 100644
--- a/src/reader/wgsl/lexer_test.cc
+++ b/src/reader/wgsl/lexer_test.cc
@@ -433,6 +433,7 @@
TokenData{"fallthrough", Token::Type::kFallthrough},
TokenData{"false", Token::Type::kFalse},
TokenData{"fn", Token::Type::kFn},
+ TokenData{"for", Token::Type::kFor},
TokenData{"fragment", Token::Type::kFragment},
TokenData{"function", Token::Type::kFunction},
TokenData{"i32", Token::Type::kI32},
@@ -492,7 +493,6 @@
"enum",
"f16",
"f64",
- "for",
"i8",
"i16",
"i64",
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 93d6937..dcd2b44 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -1473,6 +1473,7 @@
// | if_stmt
// | switch_stmt
// | loop_stmt
+// | for_stmt
// | func_call_stmt SEMICOLON
// | variable_stmt SEMICOLON
// | break_stmt SEMICOLON
@@ -1517,6 +1518,12 @@
if (loop != nullptr)
return loop;
+ auto stmt_for = for_stmt();
+ if (has_error())
+ return nullptr;
+ if (stmt_for != nullptr)
+ return stmt_for;
+
auto func = func_call_stmt();
if (has_error())
return nullptr;
@@ -1979,6 +1986,160 @@
std::move(continuing));
}
+ForHeader::ForHeader(std::unique_ptr<ast::Statement> _initializer,
+ std::unique_ptr<ast::Expression> _condition,
+ std::unique_ptr<ast::Statement> _continuing)
+ : initializer(std::move(_initializer)),
+ condition(std::move(_condition)),
+ continuing(std::move(_continuing)) {}
+
+ForHeader::~ForHeader() = default;
+
+// for_header
+// : (variable_stmt | assignment_stmt | func_call_stmt)?
+// SEMICOLON
+// logical_or_expression? SEMICOLON
+// (assignment_stmt | func_call_stmt)?
+std::unique_ptr<ForHeader> ParserImpl::for_header() {
+ std::unique_ptr<ast::Statement> initializer = nullptr;
+ if (initializer == nullptr) {
+ initializer = func_call_stmt();
+ if (has_error()) {
+ return nullptr;
+ }
+ }
+ if (initializer == nullptr) {
+ initializer = variable_stmt();
+ if (has_error()) {
+ return nullptr;
+ }
+ }
+ if (initializer == nullptr) {
+ initializer = assignment_stmt();
+ if (has_error()) {
+ return nullptr;
+ }
+ }
+
+ auto t = next();
+ if (!t.IsSemicolon()) {
+ set_error(t, "missing ';' after initializer in for loop");
+ return nullptr;
+ }
+
+ auto condition = logical_or_expression();
+ if (has_error()) {
+ return nullptr;
+ }
+
+ t = next();
+ if (!t.IsSemicolon()) {
+ set_error(t, "missing ';' after condition in for loop");
+ return nullptr;
+ }
+
+ std::unique_ptr<ast::Statement> continuing = nullptr;
+ if (continuing == nullptr) {
+ continuing = func_call_stmt();
+ if (has_error()) {
+ return nullptr;
+ }
+ }
+ if (continuing == nullptr) {
+ continuing = assignment_stmt();
+ if (has_error()) {
+ return nullptr;
+ }
+ }
+
+ return std::make_unique<ForHeader>(
+ std::move(initializer), std::move(condition), std::move(continuing));
+}
+
+// for_statement
+// : FOR PAREN_LEFT for_header PAREN_RIGHT BRACE_LEFT statements BRACE_RIGHT
+std::unique_ptr<ast::Statement> ParserImpl::for_stmt() {
+ auto t = peek();
+ if (!t.IsFor())
+ return nullptr;
+
+ auto source = t.source();
+ next(); // Consume the peek
+
+ t = next();
+ if (!t.IsParenLeft()) {
+ set_error(t, "missing for loop (");
+ return nullptr;
+ }
+
+ auto header = for_header();
+ if (has_error())
+ return nullptr;
+
+ t = next();
+ if (!t.IsParenRight()) {
+ set_error(t, "missing for loop )");
+ return nullptr;
+ }
+
+ t = next();
+ if (!t.IsBraceLeft()) {
+ set_error(t, "missing for loop {");
+ return nullptr;
+ }
+
+ auto body = statements();
+ if (has_error())
+ return nullptr;
+
+ t = next();
+ if (!t.IsBraceRight()) {
+ set_error(t, "missing for loop }");
+ return nullptr;
+ }
+
+ // The for statement is a syntactic sugar on top of the loop statement.
+ // We create corresponding nodes in ast with the exact same behaviour
+ // as we would expect from the loop statement.
+
+ if (header->condition != nullptr) {
+ // !condition
+ auto not_condition = std::make_unique<ast::UnaryOpExpression>(
+ header->condition->source(), ast::UnaryOp::kNot,
+ std::move(header->condition));
+ // { break; }
+ auto break_stmt =
+ std::make_unique<ast::BreakStatement>(not_condition->source());
+ auto break_body =
+ std::make_unique<ast::BlockStatement>(not_condition->source());
+ break_body->append(std::move(break_stmt));
+ // if (!condition) { break; }
+ auto break_if_not_condition = std::make_unique<ast::IfStatement>(
+ not_condition->source(), std::move(not_condition),
+ std::move(break_body));
+ body->insert(0, std::move(break_if_not_condition));
+ }
+
+ std::unique_ptr<ast::BlockStatement> continuing_body = nullptr;
+ if (header->continuing != nullptr) {
+ continuing_body =
+ std::make_unique<ast::BlockStatement>(header->continuing->source());
+ continuing_body->append(std::move(header->continuing));
+ }
+
+ auto loop = std::make_unique<ast::LoopStatement>(source, std::move(body),
+ std::move(continuing_body));
+
+ if (header->initializer != nullptr) {
+ auto result = std::make_unique<ast::BlockStatement>(source);
+ result->append(std::move(header->initializer));
+ result->append(std::move(loop));
+ return result;
+ }
+
+ return loop;
+}
+
// func_call_stmt
// : IDENT PAREN_LEFT argument_expression_list* PAREN_RIGHT
std::unique_ptr<ast::CallStatement> ParserImpl::func_call_stmt() {
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 3bd9145..28c5e33 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -52,6 +52,18 @@
class Lexer;
+struct ForHeader {
+ std::unique_ptr<ast::Statement> initializer;
+ std::unique_ptr<ast::Expression> condition;
+ std::unique_ptr<ast::Statement> continuing;
+
+ ForHeader(std::unique_ptr<ast::Statement> _initializer,
+ std::unique_ptr<ast::Expression> _condition,
+ std::unique_ptr<ast::Statement> _continuing);
+
+ ~ForHeader();
+};
+
/// ParserImpl for WGSL source data
class ParserImpl {
public:
@@ -227,6 +239,12 @@
/// Parses a `loop_stmt` grammar element
/// @returns the parsed loop or nullptr
std::unique_ptr<ast::LoopStatement> loop_stmt();
+ /// Parses a `for_header` grammar element
+ /// @returns the parsed for header or nullptr
+ std::unique_ptr<ForHeader> for_header();
+ /// Parses a `for_stmt` grammar element
+ /// @returns the parsed for loop or nullptr
+ std::unique_ptr<ast::Statement> for_stmt();
/// Parses a `continuing_stmt` grammar element
/// @returns the parsed statements
std::unique_ptr<ast::BlockStatement> continuing_stmt();
diff --git a/src/reader/wgsl/parser_impl_for_stmt_test.cc b/src/reader/wgsl/parser_impl_for_stmt_test.cc
new file mode 100644
index 0000000..44704b4
--- /dev/null
+++ b/src/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -0,0 +1,286 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gtest/gtest.h"
+#include "src/reader/wgsl/parser_impl.h"
+#include "src/reader/wgsl/parser_impl_test_helper.h"
+
+#include <string>
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+class ForStmtTest : public ParserImplTest {
+ public:
+ void TestForLoop(std::string loop_str, std::string for_str) {
+ auto* p_loop = parser(loop_str);
+ auto e_loop = p_loop->statements();
+ ASSERT_FALSE(p_loop->has_error()) << p_loop->error();
+ ASSERT_NE(e_loop, nullptr);
+
+ auto* p_for = parser(for_str);
+ auto e_for = p_for->statements();
+ ASSERT_FALSE(p_for->has_error()) << p_for->error();
+ ASSERT_NE(e_for, nullptr);
+
+ EXPECT_EQ(e_loop->str(), e_for->str());
+ }
+};
+
+// Test an empty for loop.
+TEST_F(ForStmtTest, Empty) {
+ std::string for_str = "for (;;) { }";
+ std::string loop_str = "loop { }";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop with non-empty body.
+TEST_F(ForStmtTest, Body) {
+ std::string for_str = "for (;;) { discard; }";
+ std::string loop_str = "loop { discard; }";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop declaring a variable in the initializer statement.
+TEST_F(ForStmtTest, InitializerStatementDecl) {
+ std::string for_str = "for (var i: i32 ;;) { }";
+ std::string loop_str = "{ var i: i32; loop { } }";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop declaring and initializing a variable in the initializer
+// statement.
+TEST_F(ForStmtTest, InitializerStatementDeclEqual) {
+ std::string for_str = "for (var i: i32 = 0 ;;) { }";
+ std::string loop_str = "{ var i: i32 = 0; loop { } }";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop declaring a const variable in the initializer statement.
+TEST_F(ForStmtTest, InitializerStatementConstDecl) {
+ std::string for_str = "for (const i: i32 = 0 ;;) { }";
+ std::string loop_str = "{ const i: i32 = 0; loop { } }";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop assigning a variable in the initializer statement.
+TEST_F(ForStmtTest, InitializerStatementAssignment) {
+ std::string for_str = "var i: i32; for (i = 0 ;;) { }";
+ std::string loop_str = "var i: i32; { i = 0; loop { } }";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop calling a function in the initializer statement.
+TEST_F(ForStmtTest, InitializerStatementFuncCall) {
+ std::string for_str = "for (a(b,c) ;;) { }";
+ std::string loop_str = "{ a(b,c); loop { } }";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop with a break condition
+TEST_F(ForStmtTest, BreakCondition) {
+ std::string for_str = "for (; 0 == 1;) { }";
+ std::string loop_str = "loop { if (!(0 == 1)) { break; } }";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop assigning a variable in the continuing statement.
+TEST_F(ForStmtTest, ContinuingAssignment) {
+ std::string for_str = "var x: i32; for (;; x = 2) { }";
+ std::string loop_str = "var x: i32; loop { continuing { x = 2; }}";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop calling a function in the continuing statement.
+TEST_F(ForStmtTest, ContinuingFuncCall) {
+ std::string for_str = "for (;; a(b,c)) { }";
+ std::string loop_str = "loop { continuing { a(b,c); }}";
+
+ TestForLoop(loop_str, for_str);
+}
+
+// Test a for loop with all statements non-empty.
+TEST_F(ForStmtTest, All) {
+ std::string for_str =
+ R"(for(var i : i32 = 0; i < 4; i = i + 1) {
+ if (a == 0) {
+ continue;
+ }
+ a = a + 2;
+ })";
+
+ std::string loop_str =
+ R"({ # Introduce new scope for loop variable i
+ var i : i32 = 0;
+ loop {
+ if (!(i < 4)) {
+ break;
+ }
+
+ if (a == 0) {
+ continue;
+ }
+ a = a + 2;
+
+ continuing {
+ i = i + 1;
+ }
+ }
+ };)";
+
+ TestForLoop(loop_str, for_str);
+}
+
+class ForStmtErrorTest : public ParserImplTest {
+ public:
+ void TestForWithError(std::string for_str, std::string error_str) {
+ auto* p_for = parser(for_str);
+ auto e_for = p_for->for_stmt();
+
+ ASSERT_TRUE(p_for->has_error());
+ ASSERT_EQ(e_for, nullptr);
+ EXPECT_EQ(p_for->error(), error_str);
+ }
+};
+
+// Test a for loop with missing left parenthesis is invalid.
+TEST_F(ForStmtErrorTest, MissingLeftParen) {
+ std::string for_str = "for { }";
+ std::string error_str = "1:5: missing for loop (";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing first semicolon is invalid.
+TEST_F(ForStmtErrorTest, MissingFirstSemicolon) {
+ std::string for_str = "for () {}";
+ std::string error_str = "1:6: missing ';' after initializer in for loop";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing second semicolon is invalid.
+TEST_F(ForStmtErrorTest, MissingSecondSemicolon) {
+ std::string for_str = "for (;) {}";
+ std::string error_str = "1:7: missing ';' after condition in for loop";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing right parenthesis is invalid.
+TEST_F(ForStmtErrorTest, MissingRightParen) {
+ std::string for_str = "for (;; {}";
+ std::string error_str = "1:9: missing for loop )";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing left brace is invalid.
+TEST_F(ForStmtErrorTest, MissingLeftBrace) {
+ std::string for_str = "for (;;)";
+ std::string error_str = "1:9: missing for loop {";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing right brace is invalid.
+TEST_F(ForStmtErrorTest, MissingRightBrace) {
+ std::string for_str = "for (;;) {";
+ std::string error_str = "1:11: missing for loop }";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with an invalid initializer statement.
+TEST_F(ForStmtErrorTest, InvalidInitializerAsConstDecl) {
+ std::string for_str = "for (const x: i32;;) { }";
+ std::string error_str = "1:18: missing = for constant declaration";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with a initializer statement not matching
+// variable_stmt | assignment_stmt | func_call_stmt.
+TEST_F(ForStmtErrorTest, InvalidInitializerMatch) {
+ std::string for_str = "for (if (true) {} ;;) { }";
+ std::string error_str = "1:6: missing ';' after initializer in for loop";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with an invalid break condition.
+TEST_F(ForStmtErrorTest, InvalidBreakConditionAsExpression) {
+ std::string for_str = "for (; (0 == 1; ) { }";
+ std::string error_str = "1:15: expected )";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with a break condition not matching
+// logical_or_expression.
+TEST_F(ForStmtErrorTest, InvalidBreakConditionMatch) {
+ std::string for_str = "for (; var i: i32 = 0;) { }";
+ std::string error_str = "1:8: missing ';' after condition in for loop";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with an invalid continuing statement.
+TEST_F(ForStmtErrorTest, InvalidContinuingAsFuncCall) {
+ std::string for_str = "for (;; a(,) ) { }";
+ std::string error_str = "1:11: unable to parse argument expression";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with a continuing statement not matching
+// assignment_stmt | func_call_stmt.
+TEST_F(ForStmtErrorTest, InvalidContinuingMatch) {
+ std::string for_str = "for (;; var i: i32 = 0) { }";
+ std::string error_str = "1:9: missing for loop )";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with an invalid body.
+TEST_F(ForStmtErrorTest, InvalidBody) {
+ std::string for_str = "for (;;) { const x: i32; }";
+ std::string error_str = "1:24: missing = for constant declaration";
+
+ TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with a body not matching statements
+TEST_F(ForStmtErrorTest, InvalidBodyMatch) {
+ std::string for_str = "for (;;) { fn main() -> void {} }";
+ std::string error_str = "1:12: missing for loop }";
+
+ TestForWithError(for_str, error_str);
+}
+
+} // namespace
+} // namespace wgsl
+} // namespace reader
+} // namespace tint
diff --git a/src/reader/wgsl/token.cc b/src/reader/wgsl/token.cc
index 6f9672e..2705fd2 100644
--- a/src/reader/wgsl/token.cc
+++ b/src/reader/wgsl/token.cc
@@ -149,6 +149,8 @@
return "false";
case Token::Type::kFn:
return "fn";
+ case Token::Type::kFor:
+ return "for";
case Token::Type::kFragment:
return "fragment";
case Token::Type::kFunction:
diff --git a/src/reader/wgsl/token.h b/src/reader/wgsl/token.h
index 7fbc177..9f9ba4a 100644
--- a/src/reader/wgsl/token.h
+++ b/src/reader/wgsl/token.h
@@ -160,6 +160,8 @@
kFalse,
/// A 'fn'
kFn,
+ // A 'for'
+ kFor,
/// A 'fragment'
kFragment,
/// A 'function'
@@ -415,6 +417,8 @@
bool IsFalse() const { return type_ == Type::kFalse; }
/// @returns true if token is a 'fn'
bool IsFn() const { return type_ == Type::kFn; }
+ /// @returns true if token is a 'for'
+ bool IsFor() const { return type_ == Type::kFor; }
/// @returns true if token is a 'fragment'
bool IsFragment() const { return type_ == Type::kFragment; }
/// @returns true if token is a 'function'