Repository: getmoto/moto

Issue:
DynamoDB UpdateExpression with newlines raises InvalidTokenException 
### Context

When calling an API that uses `UpdateExpression`, the `ExpressionTokenizer` raises an `InvalidTokenException` if the expression contains newlines, even though it should be an acceptable expression. This seems similar to the issue #2275, but from what I see the tokenizer's changed quite a lot since then.

It looks like moto raises the following exception when executing this [line](https://github.com/getmoto/moto/blob/4.2.11/moto/dynamodb/parsing/tokens.py#L210).

```
moto.dynamodb.exceptions.InvalidTokenException: 400 Bad Request: {"__type": "com.amazonaws.dynamodb.v20120810#ValidationException", "message": "Invalid UpdateExpression: Syntax error; token: \"\n\", near: \"\n\""}
```

I think the solution could be as simple as adding a newline token and grouping it in with the whitespaces (it worked for me), though I haven't ran tests against it.

### Example

The boto client is able to write the item without any issues, whereas the moto client fails with the following stacktrace.

```
Traceback (most recent call last):
  File "/Users/abbasyadollahi/random/issue.py", line 48, in <module>
    query(moto_client) # This raises
    ^^^^^^^^^^^^^^^^^^
  File "/Users/abbasyadollahi/random/issue.py", line 10, in query
    client.update_item(
  File "/Users/abbasyadollahi/random/env/lib/python3.11/site-packages/botocore/client.py", line 553, in _api_call
    return self._make_api_call(operation_name, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/abbasyadollahi/random/env/lib/python3.11/site-packages/botocore/client.py", line 1009, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateItem operation: Invalid UpdateExpression: Syntax error; token: "
", near: "ADD
"
```

```python
import boto3
import moto

table_name = "test"
my_key = "my_key"
my_count = "1"


def query(client):
    client.update_item(
        TableName=table_name,
        Key={
            "MyKey": {"S": my_key},
        },
        UpdateExpression=(
            """
            ADD
                MyTotalCount :MyCount
            """
        ),
        ExpressionAttributeValues={
            ":MyTotalCount": {"N": my_count},
        },
    )


boto_client = boto3.client("dynamodb")
query(boto_client)  # This passes

with moto.mock_dynamodb():
    moto_client = boto3.client("dynamodb")
    moto_client.create_table(
        TableName=table_name,
        KeySchema=[
            {
                "AttributeName": "MyKey",
                "KeyType": "HASH",
            }
        ],
        AttributeDefinitions=[
            {
                "AttributeName": "MyKey",
                "AttributeType": "S",
            }
        ],
        BillingMode="PAY_PER_REQUEST",
    )
    query(moto_client)  # This raises
```

### Versions

boto3==1.34.0
botocore==1.34.0
moto==4.2.11


Discussion:
Hi @abbasyadollahi, welcome to Moto! I'll mark this as a bug.

Resolving patch:
diff --git a/moto/dynamodb/parsing/tokens.py b/moto/dynamodb/parsing/tokens.py
--- a/moto/dynamodb/parsing/tokens.py
+++ b/moto/dynamodb/parsing/tokens.py
@@ -11,12 +11,12 @@ class Token:
     _TOKEN_INSTANCE = None
     MINUS_SIGN = "-"
     PLUS_SIGN = "+"
-    SPACE_SIGN = " "
     EQUAL_SIGN = "="
     OPEN_ROUND_BRACKET = "("
     CLOSE_ROUND_BRACKET = ")"
     COMMA = ","
     SPACE = " "
+    NEW_LINE = "\n"
     DOT = "."
     OPEN_SQUARE_BRACKET = "["
     CLOSE_SQUARE_BRACKET = "]"
@@ -24,12 +24,12 @@ class Token:
     SPECIAL_CHARACTERS = [
         MINUS_SIGN,
         PLUS_SIGN,
-        SPACE_SIGN,
         EQUAL_SIGN,
         OPEN_ROUND_BRACKET,
         CLOSE_ROUND_BRACKET,
         COMMA,
         SPACE,
+        NEW_LINE,
         DOT,
         OPEN_SQUARE_BRACKET,
         CLOSE_SQUARE_BRACKET,
@@ -193,7 +193,7 @@ def _make_list(self) -> List[Token]:
             else:
                 self.process_staged_characters()
 
-                if character == Token.SPACE:
+                if character in [Token.SPACE, Token.NEW_LINE]:
                     if (
                         len(self.token_list) > 0
                         and self.token_list[-1].type == Token.WHITESPACE


Test patch:
diff --git a/tests/test_dynamodb/test_dynamodb_update_expressions.py b/tests/test_dynamodb/test_dynamodb_update_expressions.py
--- a/tests/test_dynamodb/test_dynamodb_update_expressions.py
+++ b/tests/test_dynamodb/test_dynamodb_update_expressions.py
@@ -1,33 +1,42 @@
 import boto3
+import pytest
 
-from moto import mock_dynamodb
+from . import dynamodb_aws_verified
 
 
-@mock_dynamodb
-def test_update_different_map_elements_in_single_request():
+@pytest.mark.aws_verified
+@dynamodb_aws_verified()
+def test_update_different_map_elements_in_single_request(table_name=None):
     # https://github.com/getmoto/moto/issues/5552
     dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
-    dynamodb.create_table(
-        TableName="example_table",
-        KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
-        AttributeDefinitions=[
-            {"AttributeName": "id", "AttributeType": "S"},
-        ],
-        BillingMode="PAY_PER_REQUEST",
-    )
+
     record = {
-        "id": "example_id",
+        "pk": "example_id",
         "d": {"hello": "h", "world": "w"},
     }
-    table = dynamodb.Table("example_table")
+    table = dynamodb.Table(table_name)
     table.put_item(Item=record)
     updated = table.update_item(
-        Key={"id": "example_id"},
+        Key={"pk": "example_id"},
         UpdateExpression="set d.hello = :h, d.world = :w",
         ExpressionAttributeValues={":h": "H", ":w": "W"},
         ReturnValues="ALL_NEW",
     )
     assert updated["Attributes"] == {
-        "id": "example_id",
+        "pk": "example_id",
         "d": {"hello": "H", "world": "W"},
     }
+
+    # Use UpdateExpression that contains a new-line
+    # https://github.com/getmoto/moto/issues/7127
+    table.update_item(
+        Key={"pk": "example_id"},
+        UpdateExpression=(
+            """
+            ADD 
+              MyTotalCount :MyCount
+            """
+        ),
+        ExpressionAttributeValues={":MyCount": 5},
+    )
+    assert table.get_item(Key={"pk": "example_id"})["Item"]["MyTotalCount"] == 5