Parse void function call.

This CL updates the WGSL parser to handle a void function call.

Fixes: tint:45
Change-Id: If5b2a4b9e62f0b10e0f2e2e10c0ca2586c5268e8
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/25322
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index f6f1a9b..662c682 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -838,6 +838,7 @@
     "src/reader/wgsl/parser_impl_assignment_stmt_test.cc",
     "src/reader/wgsl/parser_impl_body_stmt_test.cc",
     "src/reader/wgsl/parser_impl_break_stmt_test.cc",
+    "src/reader/wgsl/parser_impl_call_stmt_test.cc",
     "src/reader/wgsl/parser_impl_case_body_test.cc",
     "src/reader/wgsl/parser_impl_const_expr_test.cc",
     "src/reader/wgsl/parser_impl_const_literal_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 06b02eb..d91d563 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -385,6 +385,7 @@
     reader/wgsl/parser_impl_assignment_stmt_test.cc
     reader/wgsl/parser_impl_body_stmt_test.cc
     reader/wgsl/parser_impl_break_stmt_test.cc
+    reader/wgsl/parser_impl_call_stmt_test.cc
     reader/wgsl/parser_impl_case_body_test.cc
     reader/wgsl/parser_impl_const_expr_test.cc
     reader/wgsl/parser_impl_const_literal_test.cc
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 63e426b..c3e7318 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -1422,6 +1422,7 @@
 //   | if_stmt
 //   | switch_stmt
 //   | loop_stmt
+//   | func_call_stmt SEMICOLON
 //   | variable_stmt SEMICOLON
 //   | break_stmt SEMICOLON
 //   | continue_stmt SEMICOLON
@@ -1464,6 +1465,18 @@
   if (loop != nullptr)
     return loop;
 
+  auto func = func_call_stmt();
+  if (has_error())
+    return nullptr;
+  if (func != nullptr) {
+    t = next();
+    if (!t.IsSemicolon()) {
+      set_error(t, "missing ;");
+      return nullptr;
+    }
+    return func;
+  }
+
   auto var = variable_stmt();
   if (has_error())
     return nullptr;
@@ -1908,6 +1921,41 @@
                                               std::move(continuing));
 }
 
+// func_call_stmt
+//    : IDENT PAREN_LEFT argument_expression_list* PAREN_RIGHT
+std::unique_ptr<ast::CallStatement> ParserImpl::func_call_stmt() {
+  auto t = peek();
+  auto t2 = peek(1);
+  if (!t.IsIdentifier() || !t2.IsParenLeft())
+    return nullptr;
+
+  auto source = t.source();
+
+  next();  // Consume the peek
+  next();  // Consume the 2nd peek
+
+  auto name = t.to_str();
+
+  t = peek();
+  ast::ExpressionList params;
+  if (!t.IsParenRight() && !t.IsEof()) {
+    params = argument_expression_list();
+    if (has_error())
+      return nullptr;
+  }
+
+  t = next();
+  if (!t.IsParenRight()) {
+    set_error(t, "missing ) for call statement");
+    return nullptr;
+  }
+
+  return std::make_unique<ast::CallStatement>(
+      std::make_unique<ast::CallExpression>(
+          source, std::make_unique<ast::IdentifierExpression>(name),
+          std::move(params)));
+}
+
 // break_stmt
 //   : BREAK
 std::unique_ptr<ast::BreakStatement> ParserImpl::break_stmt() {
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 38f8cbf..af76bbb 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -23,6 +23,7 @@
 
 #include "src/ast/assignment_statement.h"
 #include "src/ast/builtin.h"
+#include "src/ast/call_statement.h"
 #include "src/ast/case_statement.h"
 #include "src/ast/constructor_expression.h"
 #include "src/ast/else_statement.h"
@@ -219,6 +220,9 @@
   /// Parses a `case_body` grammar element
   /// @returns the parsed statements
   ast::StatementList case_body();
+  /// Parses a `func_call_stmt` grammar element
+  /// @returns the parsed function call or nullptr
+  std::unique_ptr<ast::CallStatement> func_call_stmt();
   /// Parses a `loop_stmt` grammar element
   /// @returns the parsed loop or nullptr
   std::unique_ptr<ast::LoopStatement> loop_stmt();
diff --git a/src/reader/wgsl/parser_impl_call_stmt_test.cc b/src/reader/wgsl/parser_impl_call_stmt_test.cc
new file mode 100644
index 0000000..1b49049
--- /dev/null
+++ b/src/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -0,0 +1,86 @@
+// 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/ast/call_expression.h"
+#include "src/ast/call_statement.h"
+#include "src/ast/identifier_expression.h"
+#include "src/reader/wgsl/parser_impl.h"
+#include "src/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Statement_Call) {
+  auto* p = parser("a();");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e, nullptr);
+
+  ASSERT_TRUE(e->IsCall());
+  auto* c = e->AsCall()->expr();
+
+  ASSERT_TRUE(c->func()->IsIdentifier());
+  auto* func = c->func()->AsIdentifier();
+  EXPECT_EQ(func->name(), "a");
+
+  EXPECT_EQ(c->params().size(), 0u);
+}
+
+TEST_F(ParserImplTest, Statement_Call_WithParams) {
+  auto* p = parser("a(1, b, 2 + 3 / b);");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e, nullptr);
+
+  ASSERT_TRUE(e->IsCall());
+  auto* c = e->AsCall()->expr();
+
+  ASSERT_TRUE(c->func()->IsIdentifier());
+  auto* func = c->func()->AsIdentifier();
+  EXPECT_EQ(func->name(), "a");
+
+  EXPECT_EQ(c->params().size(), 3u);
+  EXPECT_TRUE(c->params()[0]->IsConstructor());
+  EXPECT_TRUE(c->params()[1]->IsIdentifier());
+  EXPECT_TRUE(c->params()[2]->IsBinary());
+}
+
+TEST_F(ParserImplTest, Statement_Call_Missing_RightParen) {
+  auto* p = parser("a(");
+  auto e = p->statement();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:3: missing ) for call statement");
+}
+
+TEST_F(ParserImplTest, Statement_Call_Missing_Semi) {
+  auto* p = parser("a()");
+  auto e = p->statement();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:4: missing ;");
+}
+
+TEST_F(ParserImplTest, Statement_Call_Bad_ArgList) {
+  auto* p = parser("a(b c);");
+  auto e = p->statement();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:5: missing ) for call statement");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/test/simple.wgsl b/test/simple.wgsl
index e60ec1d..92d42ad 100644
--- a/test/simple.wgsl
+++ b/test/simple.wgsl
@@ -14,9 +14,14 @@
 
 [[location 0]] var<out> gl_FragColor : vec4<f32>;
 
+fn bar() -> void {
+
+}
+
 fn main() -> void {
     var a : vec2<f32> = vec2<f32>();
     gl_FragColor = vec4<f32>(0.4, 0.4, 0.8, 1.0);
+    bar();
     return;
 }
 entry_point fragment = main;