]> granicus.if.org Git - php/commitdiff
Implement flexible heredoc/nowdoc syntax
authorThomas Punt <tpunt@hotmail.co.uk>
Fri, 15 Sep 2017 13:07:59 +0000 (14:07 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 13 Apr 2018 19:35:37 +0000 (21:35 +0200)
RFC: https://wiki.php.net/rfc/flexible_heredoc_nowdoc_syntaxes

* The ending label no longer has to be followed by a semicolon or
  newline. Any non-label character is fine.
* The ending label may be indented. The indentation will be stripped
  from all lines in the heredoc/nowdoc string.

Lexing of heredoc strings performs a scan-ahead to determine the
indentation of the ending label, so that the correct amount of
indentation can be removed when calculting the semantic values for
use by the parser. This makes the implementation quite a bit more
complicated than we would like :/

42 files changed:
NEWS
UPGRADING
Zend/tests/flexible-heredoc-complex-test1.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-complex-test2.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-complex-test3.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-complex-test4.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error1.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error10.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error11.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error12.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error13.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error2.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error3.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error4.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error5.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error6.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error7.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error8.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-error9.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-nowdoc-lineno.phpt [new file with mode: 0644]
Zend/tests/flexible-heredoc-nowdoc.phpt [new file with mode: 0644]
Zend/tests/flexible-nowdoc-error1.phpt [new file with mode: 0644]
Zend/tests/flexible-nowdoc-error2.phpt [new file with mode: 0644]
Zend/tests/flexible-nowdoc-error3.phpt [new file with mode: 0644]
Zend/tests/flexible-nowdoc-error4.phpt [new file with mode: 0644]
Zend/tests/flexible-nowdoc-error5.phpt [new file with mode: 0644]
Zend/tests/flexible-nowdoc-error6.phpt [new file with mode: 0644]
Zend/tests/flexible-nowdoc-error7.phpt [new file with mode: 0644]
Zend/tests/flexible-nowdoc-error8.phpt [new file with mode: 0644]
Zend/tests/heredoc_009.phpt [deleted file]
Zend/tests/heredoc_010.phpt [deleted file]
Zend/tests/heredoc_017.phpt [deleted file]
Zend/tests/heredoc_018.phpt [deleted file]
Zend/tests/nowdoc_009.phpt [deleted file]
Zend/tests/nowdoc_010.phpt [deleted file]
Zend/zend_globals.h
Zend/zend_language_scanner.c
Zend/zend_language_scanner.h
Zend/zend_language_scanner.l
Zend/zend_ptr_stack.c
Zend/zend_ptr_stack.h
ext/tokenizer/tests/token_get_all_heredoc_nowdoc.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 5a88496560870de9a88dae4646677d611c1678bf..7774a7f7b9e092dcbcddf4c8cec0cf2029dd5439 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,9 @@ PHP                                                                        NEWS
   . Removed support for BeOS. (Kalle)
   . Add PHP_VERSION to phpinfo() <title/>. (github/MattJeevas)
   . Add net_get_interfaces(). (Sara, Joe, Anatol)
+  . Implemented flexible heredoc and nowdoc syntax, per
+    RFC https://wiki.php.net/rfc/flexible_heredoc_nowdoc_syntaxes.
+    (Thomas Punt)
   . Added support for references in list() and array destructuring, per
     RFC https://wiki.php.net/rfc/list_reference_assignment.
     (David Walker)
index 1267fbb348902de75a4d8633372982b2e6276a7e..94125306d620e44db427375457946f5d81f890d9 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -61,6 +61,11 @@ Standard:
 ========================================
 
 Core:
+  . Implemented flexible heredoc and nowdoc syntax: The closing marker for doc
+    strings is no longer required to be followed by a semicolon or newline.
+    Additionally the closing marker may be indented, in which case the
+    indentation will be stripped from all lines in the doc string.
+    (RFC: https://wiki.php.net/rfc/flexible_heredoc_nowdoc_syntaxes)
   . Array destructuring now supports reference assignments using the syntax
     [&$a, [$b, &$c]] = $d. The same is also supported for list().
     (RFC: https://wiki.php.net/rfc/list_reference_assignment)
diff --git a/Zend/tests/flexible-heredoc-complex-test1.phpt b/Zend/tests/flexible-heredoc-complex-test1.phpt
new file mode 100644 (file)
index 0000000..3e148d0
--- /dev/null
@@ -0,0 +1,27 @@
+--TEST--
+Flexible heredoc syntax complex test 1: interpolated nested heredocs
+with different delimiter names
+--FILE--
+<?php
+
+$a = 'b';
+${"b\nb\n d"} = 'b';
+
+var_dump(<<<DOC1
+    a
+    ${<<<DOC2
+        b
+        ${<<<DOC3
+            a
+            DOC3}
+         d
+        DOC2
+    }
+    c
+    DOC1);
+
+?>
+--EXPECT--
+string(5) "a
+b
+c"
diff --git a/Zend/tests/flexible-heredoc-complex-test2.phpt b/Zend/tests/flexible-heredoc-complex-test2.phpt
new file mode 100644 (file)
index 0000000..d585e4b
--- /dev/null
@@ -0,0 +1,27 @@
+--TEST--
+Flexible heredoc syntax complex test 2: interpolated nested heredocs
+with the same delimiter name
+--FILE--
+<?php
+
+$a = 'b';
+${"b\nb\n d"} = 'b';
+
+var_dump(<<<DOC1
+    a
+    ${<<<DOC1
+        b
+        ${<<<DOC1
+            a
+            DOC1}
+         d
+        DOC1
+    }
+    c
+    DOC1);
+
+?>
+--EXPECT--
+string(5) "a
+b
+c"
diff --git a/Zend/tests/flexible-heredoc-complex-test3.phpt b/Zend/tests/flexible-heredoc-complex-test3.phpt
new file mode 100644 (file)
index 0000000..cf68f12
--- /dev/null
@@ -0,0 +1,27 @@
+--TEST--
+Flexible heredoc syntax complex test 3: interpolated nested heredocs
+with the same delimiter name with different levels of indentation
+--FILE--
+<?php
+
+${' a'} = ' b';
+${' b'} = 'c';
+${"b\n b"} = 'b';
+
+var_dump(<<<DOC1
+      a
+     ${<<<DOC2
+        b
+        ${<<<DOC3
+             a
+            DOC3}
+        DOC2
+     }
+    c
+    DOC1);
+
+?>
+--EXPECT--
+string(8) "  a
+ b
+c"
diff --git a/Zend/tests/flexible-heredoc-complex-test4.phpt b/Zend/tests/flexible-heredoc-complex-test4.phpt
new file mode 100644 (file)
index 0000000..5be0f44
--- /dev/null
@@ -0,0 +1,35 @@
+--TEST--
+Flexible heredoc syntax complex test 4: interpolated variable with
+the same delimiter name as the heredoc
+--FILE--
+<?php
+
+{
+    $FOO = "FOO";
+    define("FOO", "FOO");
+    $b = <<<FOO
+    Test
+    ${
+        FOO
+    }
+    FOO;
+    var_dump($b);
+}
+
+{
+    $FOO = "FOO";
+    $b = <<<FOO
+        Test
+        ${
+        FOO
+        }
+    FOO;
+    var_dump($b);
+}
+
+?>
+--EXPECT--
+string(8) "Test
+FOO"
+string(16) "    Test
+    FOO"
diff --git a/Zend/tests/flexible-heredoc-error1.phpt b/Zend/tests/flexible-heredoc-error1.phpt
new file mode 100644 (file)
index 0000000..dc56d4f
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible heredoc syntax 1: different indentation for body (spaces) ending marker (tabs)
+--FILE--
+<?php
+
+echo <<<END
+          a
+         b
+        c
+               END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error10.phpt b/Zend/tests/flexible-heredoc-error10.phpt
new file mode 100644 (file)
index 0000000..6b7fe9e
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+Flexible heredoc syntax error 10: unindented variable interpolation (as first value)
+--FILE--
+<?php
+
+$var = 'Bar';
+var_dump(<<<TEST
+$var
+ TEST);
+
+?>
+--EXPECTF--
+Parse error: Invalid body indentation level (expecting an indentation level of at least 1) in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error11.phpt b/Zend/tests/flexible-heredoc-error11.phpt
new file mode 100644 (file)
index 0000000..3e71cbd
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+Flexible heredoc syntax error 11: show erroneous line in error message (variable interpolation)
+--FILE--
+<?php
+
+echo <<<END
+ a
+$a
+ END;
+
+?>
+--EXPECTF--
+Parse error: Invalid body indentation level (expecting an indentation level of at least 1) in %s on line 5
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error12.phpt b/Zend/tests/flexible-heredoc-error12.phpt
new file mode 100644 (file)
index 0000000..022c081
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+Flexible heredoc syntax error 12: show erroneous line in error message (mixed indentation)
+--FILE--
+<?php
+
+echo <<<END
+ a
+       b
+ END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line 5
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error13.phpt b/Zend/tests/flexible-heredoc-error13.phpt
new file mode 100644 (file)
index 0000000..f26b04e
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+Flexible heredoc syntax error 12: show erroneous line in error message (lacking indentation)
+--FILE--
+<?php
+
+echo <<<END
+ a
+b
+ END;
+
+?>
+--EXPECTF--
+Parse error: Invalid body indentation level (expecting an indentation level of at least 1) in %s on line 5
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error2.phpt b/Zend/tests/flexible-heredoc-error2.phpt
new file mode 100644 (file)
index 0000000..28e4d74
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible heredoc syntax 2: mixing spaces and tabs in body
+--FILE--
+<?php
+
+echo <<<END
+       a
+       b
+       c
+     END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error3.phpt b/Zend/tests/flexible-heredoc-error3.phpt
new file mode 100644 (file)
index 0000000..751f6b7
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible heredoc syntax error 3: mixing spaces and tabs in ending marker
+--FILE--
+<?php
+
+echo <<<END
+                a
+                b
+                c
+                END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error4.phpt b/Zend/tests/flexible-heredoc-error4.phpt
new file mode 100644 (file)
index 0000000..4542b01
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible heredoc syntax error 4: not enough body indentation
+--FILE--
+<?php
+
+echo <<<END
+      a
+     b
+    c
+     END;
+
+?>
+--EXPECTF--
+Parse error: Invalid body indentation level (expecting an indentation level of at least 5) in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error5.phpt b/Zend/tests/flexible-heredoc-error5.phpt
new file mode 100644 (file)
index 0000000..fdd6e5c
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Flexible heredoc syntax error 5: mixing spaces and tabs in ending marker for 0 length body
+--FILE--
+<?php
+
+echo <<<END
+        END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error6.phpt b/Zend/tests/flexible-heredoc-error6.phpt
new file mode 100644 (file)
index 0000000..c2daa72
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Flexible heredoc syntax error 6: no ending token on 0 length body
+--DESCRIPTION--
+Note: the closing ?> has been deliberately elided.
+--FILE--
+<?php
+
+echo <<<END
+--EXPECTF--
+Parse error: syntax error, unexpected end of file in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error7.phpt b/Zend/tests/flexible-heredoc-error7.phpt
new file mode 100644 (file)
index 0000000..2dbc99a
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Flexible heredoc syntax error 7: no ending token
+--DESCRIPTION--
+Note: the closing ?> has been deliberately elided.
+--FILE--
+<?php
+
+echo <<<END
+  
+--EXPECTF--
+Parse error: syntax error, unexpected end of file, expecting variable (T_VARIABLE) or heredoc end (T_END_HEREDOC) or ${ (T_DOLLAR_OPEN_CURLY_BRACES) or {$ (T_CURLY_OPEN) in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error8.phpt b/Zend/tests/flexible-heredoc-error8.phpt
new file mode 100644 (file)
index 0000000..47e4457
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+Flexible heredoc syntax error 8: don't interpret \t as indentation
+--FILE--
+<?php
+
+<<<end
+\ta
+       end);
+
+?>
+--EXPECTF--
+Parse error: Invalid body indentation level (expecting an indentation level of at least 1) in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-error9.phpt b/Zend/tests/flexible-heredoc-error9.phpt
new file mode 100644 (file)
index 0000000..70777f2
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible heredoc syntax error 9: unindented variable interpolation
+--FILE--
+<?php
+
+$var = 'Bar';
+var_dump(<<<TEST
+  Foo
+$var
+  TEST);
+
+?>
+--EXPECTF--
+Parse error: Invalid body indentation level (expecting an indentation level of at least 2) in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-heredoc-nowdoc-lineno.phpt b/Zend/tests/flexible-heredoc-nowdoc-lineno.phpt
new file mode 100644 (file)
index 0000000..8aeddbd
--- /dev/null
@@ -0,0 +1,30 @@
+--TEST--
+Flexible heredoc lineno: ensure the compiler globals line number is correct
+--FILE--
+<?php
+
+$heredoc = <<<EOT
+hello world
+EOT;
+
+$heredoc = <<<'EOT'
+hello world
+EOT;
+
+$heredoc = <<<EOT
+ hello world
+ EOT;
+
+$heredoc = <<<'EOT'
+ hello world
+ EOT;
+
+try {
+       throw new exception();
+} catch (Exception $e) {
+       var_dump($e->getLine());
+}
+
+?>
+--EXPECT--
+int(20)
diff --git a/Zend/tests/flexible-heredoc-nowdoc.phpt b/Zend/tests/flexible-heredoc-nowdoc.phpt
new file mode 100644 (file)
index 0000000..175b9c6
--- /dev/null
@@ -0,0 +1,128 @@
+--TEST--
+Flexible heredoc/nowdoc syntax
+--FILE--
+<?php
+
+$test = 'c';
+
+var_dump(<<<'END'
+ END);
+
+var_dump(<<<END
+
+  END);
+
+// Insufficient indentation is fine if the line is whitespace-only
+// Using eval() here to avoid issue with trailing whitespace trimming
+var_dump(eval("return <<<END
+\x20 
+\x20\x20END;"));
+
+echo <<<'END'
+     a
+    b
+
+   c
+
+  d
+ e
+ END, PHP_EOL;
+
+echo <<<END
+           a
+          b
+         $test
+        d
+       e
+       END, PHP_EOL;
+
+echo <<<'END'
+
+    a
+
+   b
+
+  c
+
+ d
+
+e
+
+END, PHP_EOL;
+
+echo <<<END
+       a\r\n
+\ta\n
+   b\r\n
+  $test\n
+ d\r\n
+e\n
+END, PHP_EOL;
+
+echo <<<'END'
+    a
+   b
+  c
+ d
+e
+END, PHP_EOL;
+
+$var = 'Bar';
+var_dump(<<<TEST
+$var
+TEST);
+
+$var = 'Bar';
+var_dump(<<<TEST
+
+$var
+TEST);
+
+?>
+--EXPECT--
+string(0) ""
+string(0) ""
+string(0) ""
+    a
+   b
+
+  c
+
+ d
+e
+    a
+   b
+  c
+ d
+e
+
+    a
+
+   b
+
+  c
+
+ d
+
+e
+
+       a
+
+       a
+
+   b
+
+  c
+
+ d
+
+e
+
+    a
+   b
+  c
+ d
+e
+string(3) "Bar"
+string(4) "
+Bar"
\ No newline at end of file
diff --git a/Zend/tests/flexible-nowdoc-error1.phpt b/Zend/tests/flexible-nowdoc-error1.phpt
new file mode 100644 (file)
index 0000000..5c086d2
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible nowdoc syntax 1: different indentation for body (spaces) ending marker (tabs)
+--FILE--
+<?php
+
+echo <<<'END'
+          a
+         b
+        c
+               END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-nowdoc-error2.phpt b/Zend/tests/flexible-nowdoc-error2.phpt
new file mode 100644 (file)
index 0000000..e6bd544
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible nowdoc syntax 2: mixing spaces and tabs in body
+--FILE--
+<?php
+
+echo <<<'END'
+       a
+       b
+       c
+     END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-nowdoc-error3.phpt b/Zend/tests/flexible-nowdoc-error3.phpt
new file mode 100644 (file)
index 0000000..c50dd4f
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible nowdoc syntax error 3: mixing spaces and tabs in ending marker
+--FILE--
+<?php
+
+echo <<<'END'
+                a
+                b
+                c
+                END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-nowdoc-error4.phpt b/Zend/tests/flexible-nowdoc-error4.phpt
new file mode 100644 (file)
index 0000000..650a9a9
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Flexible nowdoc syntax error 4: not enough body indentation
+--FILE--
+<?php
+
+echo <<<'END'
+      a
+     b
+    c
+     END;
+
+?>
+--EXPECTF--
+Parse error: Invalid body indentation level (expecting an indentation level of at least 5) in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-nowdoc-error5.phpt b/Zend/tests/flexible-nowdoc-error5.phpt
new file mode 100644 (file)
index 0000000..7579c10
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Flexible nowdoc syntax error 5: mixing spaces and tabs in ending marker for 0 length body
+--FILE--
+<?php
+
+echo <<<'END'
+        END;
+
+?>
+--EXPECTF--
+Parse error: Invalid indentation - tabs and spaces cannot be mixed in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-nowdoc-error6.phpt b/Zend/tests/flexible-nowdoc-error6.phpt
new file mode 100644 (file)
index 0000000..e4f9ded
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Flexible nowdoc syntax error 6: no ending token on 0 length body
+--DESCRIPTION--
+Note: the closing ?> has been deliberately elided.
+--FILE--
+<?php
+
+echo <<<'END'
+--EXPECTF--
+Parse error: syntax error, unexpected end of file in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-nowdoc-error7.phpt b/Zend/tests/flexible-nowdoc-error7.phpt
new file mode 100644 (file)
index 0000000..00d6604
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Flexible nowdoc syntax error 7: no ending token
+--DESCRIPTION--
+Note: the closing ?> has been deliberately elided.
+--FILE--
+<?php
+
+echo <<<'END'
+  
+--EXPECTF--
+Parse error: syntax error, unexpected end of file, expecting variable (T_VARIABLE) or heredoc end (T_END_HEREDOC) or ${ (T_DOLLAR_OPEN_CURLY_BRACES) or {$ (T_CURLY_OPEN) in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/flexible-nowdoc-error8.phpt b/Zend/tests/flexible-nowdoc-error8.phpt
new file mode 100644 (file)
index 0000000..300b3b8
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Flexible nowdoc syntax error 8: no ending token with explicit trailing space
+--FILE--
+<?php
+
+eval('<<<\'end\'
+  ');
+
+?>
+--EXPECTF--
+Parse error: syntax error, unexpected end of file, expecting variable (T_VARIABLE) or heredoc end (T_END_HEREDOC) or ${ (T_DOLLAR_OPEN_CURLY_BRACES) or {$ (T_CURLY_OPEN) in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/heredoc_009.phpt b/Zend/tests/heredoc_009.phpt
deleted file mode 100644 (file)
index 38f5d28..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
---TEST--
-Torture the T_END_HEREDOC rules (heredoc)
---FILE--
-<?php
-
-require_once 'nowdoc.inc';
-
-print <<<ENDOFHEREDOC
-ENDOFHEREDOC    ;
-    ENDOFHEREDOC;
-ENDOFHEREDOC    
-    ENDOFHEREDOC
-$ENDOFHEREDOC;
-
-ENDOFHEREDOC;
-
-$x = <<<ENDOFHEREDOC
-ENDOFHEREDOC    ;
-    ENDOFHEREDOC;
-ENDOFHEREDOC    
-    ENDOFHEREDOC
-$ENDOFHEREDOC;
-
-ENDOFHEREDOC;
-
-print "{$x}";
-
-?>
---EXPECTF--
-Notice: Undefined variable: ENDOFHEREDOC in %s on line %d
-ENDOFHEREDOC    ;
-    ENDOFHEREDOC;
-ENDOFHEREDOC    
-    ENDOFHEREDOC
-;
-
-Notice: Undefined variable: ENDOFHEREDOC in %s on line %d
-ENDOFHEREDOC    ;
-    ENDOFHEREDOC;
-ENDOFHEREDOC    
-    ENDOFHEREDOC
-;
diff --git a/Zend/tests/heredoc_010.phpt b/Zend/tests/heredoc_010.phpt
deleted file mode 100644 (file)
index 5aa0433..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
---TEST--
-Torture the T_END_HEREDOC rules with variable expansions (heredoc)
---FILE--
-<?php
-
-require_once 'nowdoc.inc';
-$fooledYou = '';
-
-print <<<ENDOFHEREDOC
-{$fooledYou}ENDOFHEREDOC{$fooledYou}
-ENDOFHEREDOC{$fooledYou}
-{$fooledYou}ENDOFHEREDOC
-
-ENDOFHEREDOC;
-
-$x = <<<ENDOFHEREDOC
-{$fooledYou}ENDOFHEREDOC{$fooledYou}
-ENDOFHEREDOC{$fooledYou}
-{$fooledYou}ENDOFHEREDOC
-
-ENDOFHEREDOC;
-
-print "{$x}";
-
-?>
---EXPECT--
-ENDOFHEREDOC
-ENDOFHEREDOC
-ENDOFHEREDOC
-ENDOFHEREDOC
-ENDOFHEREDOC
-ENDOFHEREDOC
diff --git a/Zend/tests/heredoc_017.phpt b/Zend/tests/heredoc_017.phpt
deleted file mode 100644 (file)
index e0ffddf..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
---TEST--
-Testinh heredoc syntax
---FILE--
-<?php 
-
-$a = <<<A
-       A;
-;
- A;
-\;
-A;
-
-var_dump(strlen($a) == 12);
-
-?>
---EXPECT--
-bool(true)
diff --git a/Zend/tests/heredoc_018.phpt b/Zend/tests/heredoc_018.phpt
deleted file mode 100644 (file)
index c10e9c1..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
---TEST--
-Testing heredoc with tabs before identifier
---FILE--
-<?php
-
-$heredoc = <<< A
-
-foo
-
-       A;
-A;
-
-var_dump(strlen($heredoc) == 9);
-
-?>
---EXPECT--
-bool(true)
diff --git a/Zend/tests/nowdoc_009.phpt b/Zend/tests/nowdoc_009.phpt
deleted file mode 100644 (file)
index ec8b78f..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
---TEST--
-Torture the T_END_NOWDOC rules (nowdoc)
---FILE--
-<?php
-
-require_once 'nowdoc.inc';
-
-print <<<'ENDOFNOWDOC'
-ENDOFNOWDOC    ;
-    ENDOFNOWDOC;
-ENDOFNOWDOC    
-    ENDOFNOWDOC
-$ENDOFNOWDOC;
-
-ENDOFNOWDOC;
-
-$x = <<<'ENDOFNOWDOC'
-ENDOFNOWDOC    ;
-    ENDOFNOWDOC;
-ENDOFNOWDOC    
-    ENDOFNOWDOC
-$ENDOFNOWDOC;
-
-ENDOFNOWDOC;
-
-print "{$x}";
-
-?>
---EXPECT--
-ENDOFNOWDOC    ;
-    ENDOFNOWDOC;
-ENDOFNOWDOC    
-    ENDOFNOWDOC
-$ENDOFNOWDOC;
-ENDOFNOWDOC    ;
-    ENDOFNOWDOC;
-ENDOFNOWDOC    
-    ENDOFNOWDOC
-$ENDOFNOWDOC;
-
diff --git a/Zend/tests/nowdoc_010.phpt b/Zend/tests/nowdoc_010.phpt
deleted file mode 100644 (file)
index 6f28815..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
---TEST--
-Torture the T_END_NOWDOC rules with variable expansions (nowdoc)
---FILE--
-<?php
-
-require_once 'nowdoc.inc';
-$fooledYou = '';
-
-print <<<'ENDOFNOWDOC'
-{$fooledYou}ENDOFNOWDOC{$fooledYou}
-ENDOFNOWDOC{$fooledYou}
-{$fooledYou}ENDOFNOWDOC
-
-ENDOFNOWDOC;
-
-$x = <<<'ENDOFNOWDOC'
-{$fooledYou}ENDOFNOWDOC{$fooledYou}
-ENDOFNOWDOC{$fooledYou}
-{$fooledYou}ENDOFNOWDOC
-
-ENDOFNOWDOC;
-
-print "{$x}";
-
-?>
---EXPECT--
-{$fooledYou}ENDOFNOWDOC{$fooledYou}
-ENDOFNOWDOC{$fooledYou}
-{$fooledYou}ENDOFNOWDOC
-{$fooledYou}ENDOFNOWDOC{$fooledYou}
-ENDOFNOWDOC{$fooledYou}
-{$fooledYou}ENDOFNOWDOC
-
index 0db44016292a66c2f2ffcade435ea0eed12f8f24..01b5583f0a18be860c0a25fa9abd69cd803c7b0e 100644 (file)
@@ -273,6 +273,9 @@ struct _zend_php_scanner_globals {
        int yy_state;
        zend_stack state_stack;
        zend_ptr_stack heredoc_label_stack;
+       zend_bool heredoc_scan_ahead;
+       int heredoc_indentation;
+       zend_bool heredoc_indentation_uses_spaces;
 
        /* original (unfiltered) script */
        unsigned char *script_org;
index 0ef393d6e9aeefa6f5842e67cf92f54bf7b97de4..42e209ab91bf9d2990acd21899159be1dc4b8ddd 100644 (file)
@@ -183,6 +183,7 @@ void startup_scanner(void)
        CG(extra_fn_flags) = 0;
        zend_stack_init(&SCNG(state_stack), sizeof(int));
        zend_ptr_stack_init(&SCNG(heredoc_label_stack));
+       SCNG(heredoc_scan_ahead) = 0;
 }
 
 static void heredoc_label_dtor(zend_heredoc_label *heredoc_label) {
@@ -196,6 +197,7 @@ void shutdown_scanner(void)
        zend_stack_destroy(&SCNG(state_stack));
        zend_ptr_stack_clean(&SCNG(heredoc_label_stack), (void (*)(void *)) &heredoc_label_dtor, 1);
        zend_ptr_stack_destroy(&SCNG(heredoc_label_stack));
+       SCNG(heredoc_scan_ahead) = 0;
        SCNG(on_event) = NULL;
 }
 
@@ -1106,6 +1108,88 @@ skip_escape_conversion:
        return SUCCESS;
 }
 
+#define HEREDOC_USING_SPACES 1
+#define HEREDOC_USING_TABS 2
+
+static zend_bool strip_multiline_string_indentation(zval *zendlval, int newline, int indentation, zend_bool using_spaces)
+{
+       int len = Z_STRLEN_P(zendlval), new_len = len, i = 0, j = 0, skip, newline_count = 0;
+       char *copy = Z_STRVAL_P(zendlval);
+       zend_bool trailing_newline = 0;
+
+       while (j < len) {
+               trailing_newline = 0;
+
+               for (skip = 0; skip < indentation; ++skip, ++j, --new_len) {
+                       if (copy[j] == '\r' || copy[j] == '\n') {
+                               goto skip;
+                       }
+
+                       if (copy[j] != ' ' && copy[j] != '\t') {
+                               CG(zend_lineno) += newline_count;
+                               zend_throw_exception_ex(zend_ce_parse_error, 0, "Invalid body indentation level (expecting an indentation level of at least %d)", indentation);
+                               goto error;
+                       }
+
+                       if ((!using_spaces && copy[j] == ' ') || (using_spaces && copy[j] == '\t')) {
+                               CG(zend_lineno) += newline_count;
+                               zend_throw_exception(zend_ce_parse_error, "Invalid indentation - tabs and spaces cannot be mixed", 0);
+                               goto error;
+                       }
+               }
+
+               while (j < len && copy[j] != '\r' && copy[j] != '\n') {
+                       copy[i++] = copy[j++];
+               }
+
+               if (j == len) {
+                       break;
+               }
+skip:
+               if (copy[j] == '\r') {
+                       copy[i++] = copy[j++];
+                       trailing_newline = 1;
+               }
+
+               if (copy[j] == '\n') {
+                       copy[i++] = copy[j++];
+                       trailing_newline = 1;
+               }
+
+               if (trailing_newline) {
+                       ++newline_count;
+               }
+       }
+
+       if (YYSTATE != STATE(ST_END_HEREDOC) && trailing_newline && indentation) {
+               CG(zend_lineno) += newline_count;
+               zend_throw_exception_ex(zend_ce_parse_error, 0, "Invalid body indentation level (expecting an indentation level of at least %d)", indentation);
+               goto error;
+       }
+
+       Z_STRVAL_P(zendlval)[new_len - newline] = '\0';
+       Z_STRLEN_P(zendlval) = new_len - newline;
+
+       return 1;
+
+error:
+       zval_dtor(zendlval);
+       ZVAL_UNDEF(zendlval);
+
+       return 0;
+}
+
+static void copy_heredoc_label_stack(void *void_heredoc_label)
+{
+       zend_heredoc_label *heredoc_label = void_heredoc_label;
+       zend_heredoc_label *new_heredoc_label = emalloc(sizeof(zend_heredoc_label));
+
+       *new_heredoc_label = *heredoc_label;
+       new_heredoc_label->label = estrndup(heredoc_label->label, heredoc_label->length);
+
+       zend_ptr_stack_push(&SCNG(heredoc_label_stack), (void *) new_heredoc_label);
+}
+
 #define PARSER_MODE() \
        EXPECTED(elem != NULL)
 
@@ -1141,7 +1225,7 @@ restart:
        SCNG(yy_text) = YYCURSOR;
 
 
-#line 1145 "Zend/zend_language_scanner.c"
+#line 1229 "Zend/zend_language_scanner.c"
 {
        YYCTYPE yych;
        unsigned int yyaccept = 0;
@@ -1193,7 +1277,7 @@ yyc_INITIAL:
 yy4:
        YYDEBUG(4, *YYCURSOR);
        yyleng = YYCURSOR - SCNG(yy_text);
-#line 1854 "Zend/zend_language_scanner.l"
+#line 1938 "Zend/zend_language_scanner.l"
        {
        if (YYCURSOR > YYLIMIT) {
                RETURN_TOKEN(END);
@@ -1240,7 +1324,7 @@ inline_char_handler:
        HANDLE_NEWLINES(yytext, yyleng);
        RETURN_TOKEN_WITH_VAL(T_INLINE_HTML);
 }
-#line 1244 "Zend/zend_language_scanner.c"
+#line 1328 "Zend/zend_language_scanner.c"
 yy5:
        YYDEBUG(5, *YYCURSOR);
        yych = *++YYCURSOR;
@@ -1256,7 +1340,7 @@ yy5:
 yy7:
        YYDEBUG(7, *YYCURSOR);
        yyleng = YYCURSOR - SCNG(yy_text);
-#line 1842 "Zend/zend_language_scanner.l"
+#line 1926 "Zend/zend_language_scanner.l"
        {
        if (CG(short_tags)) {
                BEGIN(ST_IN_SCRIPTING);
@@ -1268,13 +1352,13 @@ yy7:
                goto inline_char_handler;
        }
 }
-#line 1272 "Zend/zend_language_scanner.c"
+#line 1356 "Zend/zend_language_scanner.c"
 yy8:
        YYDEBUG(8, *YYCURSOR);
        ++YYCURSOR;
        YYDEBUG(9, *YYCURSOR);
        yyleng = YYCURSOR - SCNG(yy_text);
-#line 1823 "Zend/zend_language_scanner.l"
+#line 1907 "Zend/zend_language_scanner.l"
        {
        BEGIN(ST_IN_SCRIPTING);
        if (PARSER_MODE()) {
@@ -1282,7 +1366,7 @@ yy8:
        }
        RETURN_TOKEN(T_OPEN_TAG_WITH_ECHO);
 }
-#line 1286 "Zend/zend_language_scanner.c"
+#line 1370 "Zend/zend_language_scanner.c"
 yy10:
        YYDEBUG(10, *YYCURSOR);
        yych = *++YYCURSOR;
@@ -1313,7 +1397,7 @@ yy14:
 yy15:
        YYDEBUG(15, *YYCURSOR);
        yyleng = YYCURSOR - SCNG(yy_text);
-#line 1832 "Zend/zend_language_scanner.l"
+#line 1916 "Zend/zend_language_scanner.l"
        {
        HANDLE_NEWLINE(yytext[yyleng-1]);
        BEGIN(ST_IN_SCRIPTING);
@@ -1322,7 +1406,7 @@ yy15:
        }
        RETURN_TOKEN(T_OPEN_TAG);
 }
-#line 1326 "Zend/zend_language_scanner.c"
+#line 1410 "Zend/zend_language_scanner.c"
 yy16:
        YYDEBUG(16, *YYCURSOR);
        ++YYCURSOR;
@@ -1379,7 +1463,7 @@ yyc_ST_BACKQUOTE:
 yy20:
                YYDEBUG(20, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2305 "Zend/zend_language_scanner.l"
+#line 2472 "Zend/zend_language_scanner.l"
                {
        if (YYCURSOR > YYLIMIT) {
                RETURN_TOKEN(END);
@@ -1424,7 +1508,7 @@ yy20:
                RETURN_TOKEN(T_ERROR);
        }
 }
-#line 1428 "Zend/zend_language_scanner.c"
+#line 1512 "Zend/zend_language_scanner.c"
 yy21:
                YYDEBUG(21, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -1448,12 +1532,12 @@ yy22:
                ++YYCURSOR;
                YYDEBUG(23, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2245 "Zend/zend_language_scanner.l"
+#line 2412 "Zend/zend_language_scanner.l"
                {
        BEGIN(ST_IN_SCRIPTING);
        RETURN_TOKEN('`');
 }
-#line 1457 "Zend/zend_language_scanner.c"
+#line 1541 "Zend/zend_language_scanner.c"
 yy24:
                YYDEBUG(24, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -1474,34 +1558,34 @@ yy25:
 yy27:
                YYDEBUG(27, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1919 "Zend/zend_language_scanner.l"
+#line 2003 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 1482 "Zend/zend_language_scanner.c"
+#line 1566 "Zend/zend_language_scanner.c"
 yy28:
                YYDEBUG(28, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(29, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1619 "Zend/zend_language_scanner.l"
+#line 1703 "Zend/zend_language_scanner.l"
                {
        yy_push_state(ST_LOOKING_FOR_VARNAME);
        RETURN_TOKEN(T_DOLLAR_OPEN_CURLY_BRACES);
 }
-#line 1493 "Zend/zend_language_scanner.c"
+#line 1577 "Zend/zend_language_scanner.c"
 yy30:
                YYDEBUG(30, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(31, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2233 "Zend/zend_language_scanner.l"
+#line 2400 "Zend/zend_language_scanner.l"
                {
        yy_push_state(ST_IN_SCRIPTING);
        yyless(1);
        RETURN_TOKEN(T_CURLY_OPEN);
 }
-#line 1505 "Zend/zend_language_scanner.c"
+#line 1589 "Zend/zend_language_scanner.c"
 yy32:
                YYDEBUG(32, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -1515,13 +1599,13 @@ yy34:
                ++YYCURSOR;
                YYDEBUG(35, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1913 "Zend/zend_language_scanner.l"
+#line 1997 "Zend/zend_language_scanner.l"
                {
        yyless(yyleng - 1);
        yy_push_state(ST_VAR_OFFSET);
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 1525 "Zend/zend_language_scanner.c"
+#line 1609 "Zend/zend_language_scanner.c"
 yy36:
                YYDEBUG(36, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -1539,13 +1623,13 @@ yy37:
                ++YYCURSOR;
                YYDEBUG(38, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1905 "Zend/zend_language_scanner.l"
+#line 1989 "Zend/zend_language_scanner.l"
                {
        yyless(yyleng - 3);
        yy_push_state(ST_LOOKING_FOR_PROPERTY);
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 1549 "Zend/zend_language_scanner.c"
+#line 1633 "Zend/zend_language_scanner.c"
        }
 /* *********************************** */
 yyc_ST_DOUBLE_QUOTES:
@@ -1598,7 +1682,7 @@ yyc_ST_DOUBLE_QUOTES:
 yy42:
                YYDEBUG(42, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2251 "Zend/zend_language_scanner.l"
+#line 2418 "Zend/zend_language_scanner.l"
                {
        if (GET_DOUBLE_QUOTES_SCANNED_LENGTH()) {
                YYCURSOR += GET_DOUBLE_QUOTES_SCANNED_LENGTH() - 1;
@@ -1651,18 +1735,18 @@ double_quotes_scan_done:
                RETURN_TOKEN(T_ERROR);
        }
 }
-#line 1655 "Zend/zend_language_scanner.c"
+#line 1739 "Zend/zend_language_scanner.c"
 yy43:
                YYDEBUG(43, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(44, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2240 "Zend/zend_language_scanner.l"
+#line 2407 "Zend/zend_language_scanner.l"
                {
        BEGIN(ST_IN_SCRIPTING);
        RETURN_TOKEN('"');
 }
-#line 1666 "Zend/zend_language_scanner.c"
+#line 1750 "Zend/zend_language_scanner.c"
 yy45:
                YYDEBUG(45, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -1701,34 +1785,34 @@ yy47:
 yy49:
                YYDEBUG(49, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1919 "Zend/zend_language_scanner.l"
+#line 2003 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 1709 "Zend/zend_language_scanner.c"
+#line 1793 "Zend/zend_language_scanner.c"
 yy50:
                YYDEBUG(50, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(51, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1619 "Zend/zend_language_scanner.l"
+#line 1703 "Zend/zend_language_scanner.l"
                {
        yy_push_state(ST_LOOKING_FOR_VARNAME);
        RETURN_TOKEN(T_DOLLAR_OPEN_CURLY_BRACES);
 }
-#line 1720 "Zend/zend_language_scanner.c"
+#line 1804 "Zend/zend_language_scanner.c"
 yy52:
                YYDEBUG(52, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(53, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2233 "Zend/zend_language_scanner.l"
+#line 2400 "Zend/zend_language_scanner.l"
                {
        yy_push_state(ST_IN_SCRIPTING);
        yyless(1);
        RETURN_TOKEN(T_CURLY_OPEN);
 }
-#line 1732 "Zend/zend_language_scanner.c"
+#line 1816 "Zend/zend_language_scanner.c"
 yy54:
                YYDEBUG(54, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -1742,13 +1826,13 @@ yy56:
                ++YYCURSOR;
                YYDEBUG(57, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1913 "Zend/zend_language_scanner.l"
+#line 1997 "Zend/zend_language_scanner.l"
                {
        yyless(yyleng - 1);
        yy_push_state(ST_VAR_OFFSET);
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 1752 "Zend/zend_language_scanner.c"
+#line 1836 "Zend/zend_language_scanner.c"
 yy58:
                YYDEBUG(58, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -1766,13 +1850,13 @@ yy59:
                ++YYCURSOR;
                YYDEBUG(60, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1905 "Zend/zend_language_scanner.l"
+#line 1989 "Zend/zend_language_scanner.l"
                {
        yyless(yyleng - 3);
        yy_push_state(ST_LOOKING_FOR_PROPERTY);
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 1776 "Zend/zend_language_scanner.c"
+#line 1860 "Zend/zend_language_scanner.c"
        }
 /* *********************************** */
 yyc_ST_END_HEREDOC:
@@ -1783,12 +1867,12 @@ yyc_ST_END_HEREDOC:
        ++YYCURSOR;
        YYDEBUG(64, *YYCURSOR);
        yyleng = YYCURSOR - SCNG(yy_text);
-#line 2219 "Zend/zend_language_scanner.l"
+#line 2386 "Zend/zend_language_scanner.l"
        {
        zend_heredoc_label *heredoc_label = zend_ptr_stack_pop(&SCNG(heredoc_label_stack));
 
-       YYCURSOR += heredoc_label->length - 1;
-       yyleng = heredoc_label->length;
+       yyleng = heredoc_label->indentation + heredoc_label->length;
+       YYCURSOR += yyleng - 1;
 
        heredoc_label_dtor(heredoc_label);
        efree(heredoc_label);
@@ -1796,7 +1880,7 @@ yyc_ST_END_HEREDOC:
        BEGIN(ST_IN_SCRIPTING);
        RETURN_TOKEN(T_END_HEREDOC);
 }
-#line 1800 "Zend/zend_language_scanner.c"
+#line 1884 "Zend/zend_language_scanner.c"
 /* *********************************** */
 yyc_ST_HEREDOC:
        {
@@ -1844,11 +1928,10 @@ yyc_ST_HEREDOC:
 yy68:
                YYDEBUG(68, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2351 "Zend/zend_language_scanner.l"
+#line 2518 "Zend/zend_language_scanner.l"
                {
-       int newline = 0;
-
        zend_heredoc_label *heredoc_label = zend_ptr_stack_top(&SCNG(heredoc_label_stack));
+       int newline = 0, indentation = 0, spacing = 0;
 
        if (YYCURSOR > YYLIMIT) {
                RETURN_TOKEN(END);
@@ -1864,28 +1947,55 @@ yy68:
                                }
                                /* fall through */
                        case '\n':
+                               indentation = spacing = 0;
+
+                               while (YYCURSOR < YYLIMIT && (*YYCURSOR == ' ' || *YYCURSOR == '\t')) {
+                                       if (*YYCURSOR == '\t') {
+                                               spacing |= HEREDOC_USING_TABS;
+                                       } else {
+                                               spacing |= HEREDOC_USING_SPACES;
+                                       }
+                                       ++YYCURSOR;
+                                       ++indentation;
+                               }
+
+                               if (YYCURSOR == YYLIMIT) {
+                                       yyleng = YYCURSOR - SCNG(yy_text);
+                                       HANDLE_NEWLINES(yytext, yyleng);
+                                       ZVAL_NULL(zendlval);
+                                       RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
+                               }
+
                                /* Check for ending label on the next line */
                                if (IS_LABEL_START(*YYCURSOR) && heredoc_label->length < YYLIMIT - YYCURSOR && !memcmp(YYCURSOR, heredoc_label->label, heredoc_label->length)) {
-                                       YYCTYPE *end = YYCURSOR + heredoc_label->length;
+                                       if (IS_LABEL_START(YYCURSOR[heredoc_label->length])) {
+                                               continue;
+                                       }
 
-                                       if (*end == ';') {
-                                               end++;
+                                       if (spacing == (HEREDOC_USING_SPACES | HEREDOC_USING_TABS)) {
+                                               zend_throw_exception(zend_ce_parse_error, "Invalid indentation - tabs and spaces cannot be mixed", 0);
                                        }
 
-                                       if (*end == '\n' || *end == '\r') {
-                                               /* newline before label will be subtracted from returned text, but
-                                                * yyleng/yytext will include it, for zend_highlight/strip, tokenizer, etc. */
-                                               if (YYCURSOR[-2] == '\r' && YYCURSOR[-1] == '\n') {
-                                                       newline = 2; /* Windows newline */
-                                               } else {
-                                                       newline = 1;
-                                               }
+                                       /* newline before label will be subtracted from returned text, but
+                                        * yyleng/yytext will include it, for zend_highlight/strip, tokenizer, etc. */
+                                       if (YYCURSOR[-indentation - 2] == '\r' && YYCURSOR[-indentation - 1] == '\n') {
+                                               newline = 2; /* Windows newline */
+                                       } else {
+                                               newline = 1;
+                                       }
 
-                                               CG(increment_lineno) = 1; /* For newline before label */
-                                               BEGIN(ST_END_HEREDOC);
+                                       CG(increment_lineno) = 1; /* For newline before label */
 
-                                               goto heredoc_scan_done;
+                                       if (SCNG(heredoc_scan_ahead)) {
+                                               SCNG(heredoc_indentation) = indentation;
+                                               SCNG(heredoc_indentation_uses_spaces) = (spacing == HEREDOC_USING_SPACES);
+                                       } else {
+                                               YYCURSOR -= indentation;
                                        }
+
+                                       BEGIN(ST_END_HEREDOC);
+
+                                       goto heredoc_scan_done;
                                }
                                continue;
                        case '$':
@@ -1912,16 +2022,30 @@ yy68:
        }
 
 heredoc_scan_done:
+
        yyleng = YYCURSOR - SCNG(yy_text);
+       ZVAL_STRINGL(zendlval, yytext, yyleng);
 
-       if (EXPECTED(zend_scan_escape_string(zendlval, yytext, yyleng - newline, 0) == SUCCESS)
-        || !PARSER_MODE()) {
-               RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
+       if (!SCNG(heredoc_scan_ahead) && !EG(exception) && PARSER_MODE()) {
+               zend_string *copy = Z_STR_P(zendlval);
+
+           if (!strip_multiline_string_indentation(zendlval, newline, heredoc_label->indentation, heredoc_label->indentation_uses_spaces)) {
+                       RETURN_TOKEN(T_ERROR);
+               }
+
+               if (UNEXPECTED(zend_scan_escape_string(zendlval, ZSTR_VAL(copy), ZSTR_LEN(copy), 0) != SUCCESS)) {
+                       zend_string_free(copy);
+                       RETURN_TOKEN(T_ERROR);
+               }
+
+               zend_string_free(copy);
        } else {
-               RETURN_TOKEN(T_ERROR);
+               HANDLE_NEWLINES(yytext, yyleng - newline);
        }
+
+       RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
 }
-#line 1925 "Zend/zend_language_scanner.c"
+#line 2049 "Zend/zend_language_scanner.c"
 yy69:
                YYDEBUG(69, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -1960,34 +2084,34 @@ yy71:
 yy73:
                YYDEBUG(73, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1919 "Zend/zend_language_scanner.l"
+#line 2003 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 1968 "Zend/zend_language_scanner.c"
+#line 2092 "Zend/zend_language_scanner.c"
 yy74:
                YYDEBUG(74, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(75, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1619 "Zend/zend_language_scanner.l"
+#line 1703 "Zend/zend_language_scanner.l"
                {
        yy_push_state(ST_LOOKING_FOR_VARNAME);
        RETURN_TOKEN(T_DOLLAR_OPEN_CURLY_BRACES);
 }
-#line 1979 "Zend/zend_language_scanner.c"
+#line 2103 "Zend/zend_language_scanner.c"
 yy76:
                YYDEBUG(76, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(77, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2233 "Zend/zend_language_scanner.l"
+#line 2400 "Zend/zend_language_scanner.l"
                {
        yy_push_state(ST_IN_SCRIPTING);
        yyless(1);
        RETURN_TOKEN(T_CURLY_OPEN);
 }
-#line 1991 "Zend/zend_language_scanner.c"
+#line 2115 "Zend/zend_language_scanner.c"
 yy78:
                YYDEBUG(78, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -2001,13 +2125,13 @@ yy80:
                ++YYCURSOR;
                YYDEBUG(81, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1913 "Zend/zend_language_scanner.l"
+#line 1997 "Zend/zend_language_scanner.l"
                {
        yyless(yyleng - 1);
        yy_push_state(ST_VAR_OFFSET);
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 2011 "Zend/zend_language_scanner.c"
+#line 2135 "Zend/zend_language_scanner.c"
 yy82:
                YYDEBUG(82, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -2025,13 +2149,13 @@ yy83:
                ++YYCURSOR;
                YYDEBUG(84, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1905 "Zend/zend_language_scanner.l"
+#line 1989 "Zend/zend_language_scanner.l"
                {
        yyless(yyleng - 3);
        yy_push_state(ST_LOOKING_FOR_PROPERTY);
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 2035 "Zend/zend_language_scanner.c"
+#line 2159 "Zend/zend_language_scanner.c"
        }
 /* *********************************** */
 yyc_ST_IN_SCRIPTING:
@@ -2200,7 +2324,7 @@ yy87:
                ++YYCURSOR;
                YYDEBUG(88, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2486 "Zend/zend_language_scanner.l"
+#line 2720 "Zend/zend_language_scanner.l"
                {
        if (YYCURSOR > YYLIMIT) {
                RETURN_TOKEN(END);
@@ -2209,7 +2333,7 @@ yy87:
        zend_error(E_COMPILE_WARNING,"Unexpected character in input:  '%c' (ASCII=%d) state=%d", yytext[0], yytext[0], YYSTATE);
        goto restart;
 }
-#line 2213 "Zend/zend_language_scanner.c"
+#line 2337 "Zend/zend_language_scanner.c"
 yy89:
                YYDEBUG(89, *YYCURSOR);
                ++YYCURSOR;
@@ -2221,11 +2345,11 @@ yy89:
                }
                YYDEBUG(91, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1325 "Zend/zend_language_scanner.l"
+#line 1409 "Zend/zend_language_scanner.l"
                {
        goto return_whitespace;
 }
-#line 2229 "Zend/zend_language_scanner.c"
+#line 2353 "Zend/zend_language_scanner.c"
 yy92:
                YYDEBUG(92, *YYCURSOR);
                ++YYCURSOR;
@@ -2233,17 +2357,17 @@ yy92:
 yy93:
                YYDEBUG(93, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1608 "Zend/zend_language_scanner.l"
+#line 1692 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(yytext[0]);
 }
-#line 2241 "Zend/zend_language_scanner.c"
+#line 2365 "Zend/zend_language_scanner.c"
 yy94:
                YYDEBUG(94, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(95, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2119 "Zend/zend_language_scanner.l"
+#line 2203 "Zend/zend_language_scanner.l"
                {
        int bprefix = (yytext[0] != '"') ? 1 : 0;
 
@@ -2288,13 +2412,13 @@ yy94:
        BEGIN(ST_DOUBLE_QUOTES);
        RETURN_TOKEN('"');
 }
-#line 2292 "Zend/zend_language_scanner.c"
+#line 2416 "Zend/zend_language_scanner.c"
 yy96:
                YYDEBUG(96, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(97, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1946 "Zend/zend_language_scanner.l"
+#line 2030 "Zend/zend_language_scanner.l"
                {
        while (YYCURSOR < YYLIMIT) {
                switch (*YYCURSOR++) {
@@ -2326,7 +2450,7 @@ yy96:
        }
        RETURN_TOKEN(T_COMMENT);
 }
-#line 2330 "Zend/zend_language_scanner.c"
+#line 2454 "Zend/zend_language_scanner.c"
 yy98:
                YYDEBUG(98, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -2357,7 +2481,7 @@ yy101:
                ++YYCURSOR;
                YYDEBUG(102, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2029 "Zend/zend_language_scanner.l"
+#line 2113 "Zend/zend_language_scanner.l"
                {
        register char *s, *t;
        char *end;
@@ -2446,7 +2570,7 @@ skip_escape_conversion:
        }
        RETURN_TOKEN_WITH_VAL(T_CONSTANT_ENCAPSED_STRING);
 }
-#line 2450 "Zend/zend_language_scanner.c"
+#line 2574 "Zend/zend_language_scanner.c"
 yy103:
                YYDEBUG(103, *YYCURSOR);
                yyaccept = 0;
@@ -2576,7 +2700,7 @@ yy110:
 yy111:
                YYDEBUG(111, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1677 "Zend/zend_language_scanner.l"
+#line 1761 "Zend/zend_language_scanner.l"
                {
        char *end;
        if (yyleng < MAX_LENGTH_OF_LONG - 1) { /* Won't overflow */
@@ -2627,7 +2751,7 @@ yy111:
        ZEND_ASSERT(!errno);
        RETURN_TOKEN_WITH_VAL(T_LNUMBER);
 }
-#line 2631 "Zend/zend_language_scanner.c"
+#line 2755 "Zend/zend_language_scanner.c"
 yy112:
                YYDEBUG(112, *YYCURSOR);
                yyaccept = 1;
@@ -2708,11 +2832,11 @@ yy119:
 yy120:
                YYDEBUG(120, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1941 "Zend/zend_language_scanner.l"
+#line 2025 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN_WITH_STR(T_STRING, 0);
 }
-#line 2716 "Zend/zend_language_scanner.c"
+#line 2840 "Zend/zend_language_scanner.c"
 yy121:
                YYDEBUG(121, *YYCURSOR);
                yyaccept = 2;
@@ -2997,11 +3121,11 @@ yy142:
                ++YYCURSOR;
                YYDEBUG(143, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1348 "Zend/zend_language_scanner.l"
+#line 1432 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_NS_SEPARATOR);
 }
-#line 3005 "Zend/zend_language_scanner.c"
+#line 3129 "Zend/zend_language_scanner.c"
 yy144:
                YYDEBUG(144, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3017,23 +3141,23 @@ yy146:
                ++YYCURSOR;
                YYDEBUG(147, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2213 "Zend/zend_language_scanner.l"
+#line 2380 "Zend/zend_language_scanner.l"
                {
        BEGIN(ST_BACKQUOTE);
        RETURN_TOKEN('`');
 }
-#line 3026 "Zend/zend_language_scanner.c"
+#line 3150 "Zend/zend_language_scanner.c"
 yy148:
                YYDEBUG(148, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(149, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1613 "Zend/zend_language_scanner.l"
+#line 1697 "Zend/zend_language_scanner.l"
                {
        yy_push_state(ST_IN_SCRIPTING);
        RETURN_TOKEN('{');
 }
-#line 3037 "Zend/zend_language_scanner.c"
+#line 3161 "Zend/zend_language_scanner.c"
 yy150:
                YYDEBUG(150, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3045,7 +3169,7 @@ yy151:
                ++YYCURSOR;
                YYDEBUG(152, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1625 "Zend/zend_language_scanner.l"
+#line 1709 "Zend/zend_language_scanner.l"
                {
        RESET_DOC_COMMENT();
        if (!zend_stack_is_empty(&SCNG(state_stack))) {
@@ -3053,7 +3177,7 @@ yy151:
        }
        RETURN_TOKEN('}');
 }
-#line 3057 "Zend/zend_language_scanner.c"
+#line 3181 "Zend/zend_language_scanner.c"
 yy153:
                YYDEBUG(153, *YYCURSOR);
                ++YYCURSOR;
@@ -3061,11 +3185,11 @@ yy153:
 yy154:
                YYDEBUG(154, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1512 "Zend/zend_language_scanner.l"
+#line 1596 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_IS_NOT_EQUAL);
 }
-#line 3069 "Zend/zend_language_scanner.c"
+#line 3193 "Zend/zend_language_scanner.c"
 yy155:
                YYDEBUG(155, *YYCURSOR);
                ++YYCURSOR;
@@ -3090,41 +3214,41 @@ yy155:
 yy157:
                YYDEBUG(157, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1919 "Zend/zend_language_scanner.l"
+#line 2003 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 3098 "Zend/zend_language_scanner.c"
+#line 3222 "Zend/zend_language_scanner.c"
 yy158:
                YYDEBUG(158, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(159, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1556 "Zend/zend_language_scanner.l"
+#line 1640 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_MOD_EQUAL);
 }
-#line 3108 "Zend/zend_language_scanner.c"
+#line 3232 "Zend/zend_language_scanner.c"
 yy160:
                YYDEBUG(160, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(161, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1584 "Zend/zend_language_scanner.l"
+#line 1668 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_BOOLEAN_AND);
 }
-#line 3118 "Zend/zend_language_scanner.c"
+#line 3242 "Zend/zend_language_scanner.c"
 yy162:
                YYDEBUG(162, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(163, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1568 "Zend/zend_language_scanner.l"
+#line 1652 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_AND_EQUAL);
 }
-#line 3128 "Zend/zend_language_scanner.c"
+#line 3252 "Zend/zend_language_scanner.c"
 yy164:
                YYDEBUG(164, *YYCURSOR);
                ++YYCURSOR;
@@ -3254,72 +3378,72 @@ yy176:
                if ((yych = *YYCURSOR) == '=') goto yy289;
                YYDEBUG(177, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1540 "Zend/zend_language_scanner.l"
+#line 1624 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_POW);
 }
-#line 3262 "Zend/zend_language_scanner.c"
+#line 3386 "Zend/zend_language_scanner.c"
 yy178:
                YYDEBUG(178, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(179, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1536 "Zend/zend_language_scanner.l"
+#line 1620 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_MUL_EQUAL);
 }
-#line 3272 "Zend/zend_language_scanner.c"
+#line 3396 "Zend/zend_language_scanner.c"
 yy180:
                YYDEBUG(180, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(181, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1492 "Zend/zend_language_scanner.l"
+#line 1576 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_INC);
 }
-#line 3282 "Zend/zend_language_scanner.c"
+#line 3406 "Zend/zend_language_scanner.c"
 yy182:
                YYDEBUG(182, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(183, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1528 "Zend/zend_language_scanner.l"
+#line 1612 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_PLUS_EQUAL);
 }
-#line 3292 "Zend/zend_language_scanner.c"
+#line 3416 "Zend/zend_language_scanner.c"
 yy184:
                YYDEBUG(184, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(185, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1496 "Zend/zend_language_scanner.l"
+#line 1580 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_DEC);
 }
-#line 3302 "Zend/zend_language_scanner.c"
+#line 3426 "Zend/zend_language_scanner.c"
 yy186:
                YYDEBUG(186, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(187, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1532 "Zend/zend_language_scanner.l"
+#line 1616 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_MINUS_EQUAL);
 }
-#line 3312 "Zend/zend_language_scanner.c"
+#line 3436 "Zend/zend_language_scanner.c"
 yy188:
                YYDEBUG(188, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(189, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1320 "Zend/zend_language_scanner.l"
+#line 1404 "Zend/zend_language_scanner.l"
                {
        yy_push_state(ST_LOOKING_FOR_PROPERTY);
        RETURN_TOKEN(T_OBJECT_OPERATOR);
 }
-#line 3323 "Zend/zend_language_scanner.c"
+#line 3447 "Zend/zend_language_scanner.c"
 yy190:
                YYDEBUG(190, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3342,7 +3466,7 @@ yy191:
 yy193:
                YYDEBUG(193, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1781 "Zend/zend_language_scanner.l"
+#line 1865 "Zend/zend_language_scanner.l"
                {
        const char *end;
 
@@ -3351,17 +3475,17 @@ yy193:
        ZEND_ASSERT(end == yytext + yyleng);
        RETURN_TOKEN_WITH_VAL(T_DNUMBER);
 }
-#line 3355 "Zend/zend_language_scanner.c"
+#line 3479 "Zend/zend_language_scanner.c"
 yy194:
                YYDEBUG(194, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(195, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1552 "Zend/zend_language_scanner.l"
+#line 1636 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CONCAT_EQUAL);
 }
-#line 3365 "Zend/zend_language_scanner.c"
+#line 3489 "Zend/zend_language_scanner.c"
 yy196:
                YYDEBUG(196, *YYCURSOR);
                yyaccept = 4;
@@ -3370,7 +3494,7 @@ yy196:
 yy197:
                YYDEBUG(197, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1978 "Zend/zend_language_scanner.l"
+#line 2062 "Zend/zend_language_scanner.l"
                {
        int doc_com;
 
@@ -3409,17 +3533,17 @@ yy197:
        }
        RETURN_TOKEN(T_COMMENT);
 }
-#line 3413 "Zend/zend_language_scanner.c"
+#line 3537 "Zend/zend_language_scanner.c"
 yy198:
                YYDEBUG(198, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(199, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1548 "Zend/zend_language_scanner.l"
+#line 1632 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_DIV_EQUAL);
 }
-#line 3423 "Zend/zend_language_scanner.c"
+#line 3547 "Zend/zend_language_scanner.c"
 yy200:
                YYDEBUG(200, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3451,11 +3575,11 @@ yy203:
                ++YYCURSOR;
                YYDEBUG(204, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1344 "Zend/zend_language_scanner.l"
+#line 1428 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_PAAMAYIM_NEKUDOTAYIM);
 }
-#line 3459 "Zend/zend_language_scanner.c"
+#line 3583 "Zend/zend_language_scanner.c"
 yy205:
                YYDEBUG(205, *YYCURSOR);
                yyaccept = 5;
@@ -3466,22 +3590,22 @@ yy205:
 yy206:
                YYDEBUG(206, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1600 "Zend/zend_language_scanner.l"
+#line 1684 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_SL);
 }
-#line 3474 "Zend/zend_language_scanner.c"
+#line 3598 "Zend/zend_language_scanner.c"
 yy207:
                YYDEBUG(207, *YYCURSOR);
                ++YYCURSOR;
                if ((yych = *YYCURSOR) == '>') goto yy307;
                YYDEBUG(208, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1520 "Zend/zend_language_scanner.l"
+#line 1604 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_IS_SMALLER_OR_EQUAL);
 }
-#line 3485 "Zend/zend_language_scanner.c"
+#line 3609 "Zend/zend_language_scanner.c"
 yy209:
                YYDEBUG(209, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3492,42 +3616,42 @@ yy210:
                if ((yych = *YYCURSOR) == '=') goto yy309;
                YYDEBUG(211, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1508 "Zend/zend_language_scanner.l"
+#line 1592 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_IS_EQUAL);
 }
-#line 3500 "Zend/zend_language_scanner.c"
+#line 3624 "Zend/zend_language_scanner.c"
 yy212:
                YYDEBUG(212, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(213, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1476 "Zend/zend_language_scanner.l"
+#line 1560 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_DOUBLE_ARROW);
 }
-#line 3510 "Zend/zend_language_scanner.c"
+#line 3634 "Zend/zend_language_scanner.c"
 yy214:
                YYDEBUG(214, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(215, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1524 "Zend/zend_language_scanner.l"
+#line 1608 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_IS_GREATER_OR_EQUAL);
 }
-#line 3520 "Zend/zend_language_scanner.c"
+#line 3644 "Zend/zend_language_scanner.c"
 yy216:
                YYDEBUG(216, *YYCURSOR);
                ++YYCURSOR;
                if ((yych = *YYCURSOR) == '=') goto yy311;
                YYDEBUG(217, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1604 "Zend/zend_language_scanner.l"
+#line 1688 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_SR);
 }
-#line 3531 "Zend/zend_language_scanner.c"
+#line 3655 "Zend/zend_language_scanner.c"
 yy218:
                YYDEBUG(218, *YYCURSOR);
                ++YYCURSOR;
@@ -3536,7 +3660,7 @@ yy218:
 yy219:
                YYDEBUG(219, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2017 "Zend/zend_language_scanner.l"
+#line 2101 "Zend/zend_language_scanner.l"
                {
        BEGIN(INITIAL);
        if (yytext[yyleng-1] != '>') {
@@ -3547,17 +3671,17 @@ yy219:
        }
        RETURN_TOKEN(T_CLOSE_TAG);
 }
-#line 3551 "Zend/zend_language_scanner.c"
+#line 3675 "Zend/zend_language_scanner.c"
 yy220:
                YYDEBUG(220, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(221, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1356 "Zend/zend_language_scanner.l"
+#line 1440 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_COALESCE);
 }
-#line 3561 "Zend/zend_language_scanner.c"
+#line 3685 "Zend/zend_language_scanner.c"
 yy222:
                YYDEBUG(222, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3584,11 +3708,11 @@ yy225:
                }
                YYDEBUG(226, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1260 "Zend/zend_language_scanner.l"
+#line 1344 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_AS);
 }
-#line 3592 "Zend/zend_language_scanner.c"
+#line 3716 "Zend/zend_language_scanner.c"
 yy227:
                YYDEBUG(227, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3674,11 +3798,11 @@ yy234:
                }
                YYDEBUG(235, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1228 "Zend/zend_language_scanner.l"
+#line 1312 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_DO);
 }
-#line 3682 "Zend/zend_language_scanner.c"
+#line 3806 "Zend/zend_language_scanner.c"
 yy236:
                YYDEBUG(236, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3763,11 +3887,11 @@ yy247:
                }
                YYDEBUG(248, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1204 "Zend/zend_language_scanner.l"
+#line 1288 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_IF);
 }
-#line 3771 "Zend/zend_language_scanner.c"
+#line 3895 "Zend/zend_language_scanner.c"
 yy249:
                YYDEBUG(249, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3828,11 +3952,11 @@ yy255:
                }
                YYDEBUG(256, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1588 "Zend/zend_language_scanner.l"
+#line 1672 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_LOGICAL_OR);
 }
-#line 3836 "Zend/zend_language_scanner.c"
+#line 3960 "Zend/zend_language_scanner.c"
 yy257:
                YYDEBUG(257, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3946,11 +4070,11 @@ yy270:
                ++YYCURSOR;
                YYDEBUG(271, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1576 "Zend/zend_language_scanner.l"
+#line 1660 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_XOR_EQUAL);
 }
-#line 3954 "Zend/zend_language_scanner.c"
+#line 4078 "Zend/zend_language_scanner.c"
 yy272:
                YYDEBUG(272, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -3978,31 +4102,31 @@ yy273:
                ++YYCURSOR;
                YYDEBUG(274, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1572 "Zend/zend_language_scanner.l"
+#line 1656 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_OR_EQUAL);
 }
-#line 3986 "Zend/zend_language_scanner.c"
+#line 4110 "Zend/zend_language_scanner.c"
 yy275:
                YYDEBUG(275, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(276, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1580 "Zend/zend_language_scanner.l"
+#line 1664 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_BOOLEAN_OR);
 }
-#line 3996 "Zend/zend_language_scanner.c"
+#line 4120 "Zend/zend_language_scanner.c"
 yy277:
                YYDEBUG(277, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(278, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1504 "Zend/zend_language_scanner.l"
+#line 1588 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_IS_NOT_IDENTICAL);
 }
-#line 4006 "Zend/zend_language_scanner.c"
+#line 4130 "Zend/zend_language_scanner.c"
 yy279:
                YYDEBUG(279, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4068,21 +4192,21 @@ yy289:
                ++YYCURSOR;
                YYDEBUG(290, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1544 "Zend/zend_language_scanner.l"
+#line 1628 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_POW_EQUAL);
 }
-#line 4076 "Zend/zend_language_scanner.c"
+#line 4200 "Zend/zend_language_scanner.c"
 yy291:
                YYDEBUG(291, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(292, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1352 "Zend/zend_language_scanner.l"
+#line 1436 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ELLIPSIS);
 }
-#line 4086 "Zend/zend_language_scanner.c"
+#line 4210 "Zend/zend_language_scanner.c"
 yy293:
                YYDEBUG(293, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4106,7 +4230,7 @@ yy294:
                }
                YYDEBUG(296, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1649 "Zend/zend_language_scanner.l"
+#line 1733 "Zend/zend_language_scanner.l"
                {
        char *bin = yytext + 2; /* Skip "0b" */
        int len = yyleng - 2;
@@ -4134,7 +4258,7 @@ yy294:
                RETURN_TOKEN_WITH_VAL(T_DNUMBER);
        }
 }
-#line 4138 "Zend/zend_language_scanner.c"
+#line 4262 "Zend/zend_language_scanner.c"
 yy297:
                YYDEBUG(297, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4160,7 +4284,7 @@ yy300:
                }
                YYDEBUG(302, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1728 "Zend/zend_language_scanner.l"
+#line 1812 "Zend/zend_language_scanner.l"
                {
        char *hex = yytext + 2; /* Skip "0x" */
        int len = yyleng - 2;
@@ -4188,7 +4312,7 @@ yy300:
                RETURN_TOKEN_WITH_VAL(T_DNUMBER);
        }
 }
-#line 4192 "Zend/zend_language_scanner.c"
+#line 4316 "Zend/zend_language_scanner.c"
 yy303:
                YYDEBUG(303, *YYCURSOR);
                ++YYCURSOR;
@@ -4223,41 +4347,41 @@ yy305:
                ++YYCURSOR;
                YYDEBUG(306, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1560 "Zend/zend_language_scanner.l"
+#line 1644 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_SL_EQUAL);
 }
-#line 4231 "Zend/zend_language_scanner.c"
+#line 4355 "Zend/zend_language_scanner.c"
 yy307:
                YYDEBUG(307, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(308, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1516 "Zend/zend_language_scanner.l"
+#line 1600 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_SPACESHIP);
 }
-#line 4241 "Zend/zend_language_scanner.c"
+#line 4365 "Zend/zend_language_scanner.c"
 yy309:
                YYDEBUG(309, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(310, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1500 "Zend/zend_language_scanner.l"
+#line 1584 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_IS_IDENTICAL);
 }
-#line 4251 "Zend/zend_language_scanner.c"
+#line 4375 "Zend/zend_language_scanner.c"
 yy311:
                YYDEBUG(311, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(312, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1564 "Zend/zend_language_scanner.l"
+#line 1648 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_SR_EQUAL);
 }
-#line 4261 "Zend/zend_language_scanner.c"
+#line 4385 "Zend/zend_language_scanner.c"
 yy313:
                YYDEBUG(313, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4281,11 +4405,11 @@ yy316:
                }
                YYDEBUG(317, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1592 "Zend/zend_language_scanner.l"
+#line 1676 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_LOGICAL_AND);
 }
-#line 4289 "Zend/zend_language_scanner.c"
+#line 4413 "Zend/zend_language_scanner.c"
 yy318:
                YYDEBUG(318, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4366,11 +4490,11 @@ yy329:
                }
                YYDEBUG(330, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1162 "Zend/zend_language_scanner.l"
+#line 1246 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_EXIT);
 }
-#line 4374 "Zend/zend_language_scanner.c"
+#line 4498 "Zend/zend_language_scanner.c"
 yy331:
                YYDEBUG(331, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4452,11 +4576,11 @@ yy339:
 yy340:
                YYDEBUG(340, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1232 "Zend/zend_language_scanner.l"
+#line 1316 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_FOR);
 }
-#line 4460 "Zend/zend_language_scanner.c"
+#line 4584 "Zend/zend_language_scanner.c"
 yy341:
                YYDEBUG(341, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4525,11 +4649,11 @@ yy351:
                }
                YYDEBUG(352, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1360 "Zend/zend_language_scanner.l"
+#line 1444 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_NEW);
 }
-#line 4533 "Zend/zend_language_scanner.c"
+#line 4657 "Zend/zend_language_scanner.c"
 yy353:
                YYDEBUG(353, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4602,11 +4726,11 @@ yy362:
                }
                YYDEBUG(363, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1188 "Zend/zend_language_scanner.l"
+#line 1272 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_TRY);
 }
-#line 4610 "Zend/zend_language_scanner.c"
+#line 4734 "Zend/zend_language_scanner.c"
 yy364:
                YYDEBUG(364, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4621,11 +4745,11 @@ yy365:
                }
                YYDEBUG(366, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1424 "Zend/zend_language_scanner.l"
+#line 1508 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_USE);
 }
-#line 4629 "Zend/zend_language_scanner.c"
+#line 4753 "Zend/zend_language_scanner.c"
 yy367:
                YYDEBUG(367, *YYCURSOR);
                ++YYCURSOR;
@@ -4634,11 +4758,11 @@ yy367:
                }
                YYDEBUG(368, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1368 "Zend/zend_language_scanner.l"
+#line 1452 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_VAR);
 }
-#line 4642 "Zend/zend_language_scanner.c"
+#line 4766 "Zend/zend_language_scanner.c"
 yy369:
                YYDEBUG(369, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4653,11 +4777,11 @@ yy370:
                }
                YYDEBUG(371, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1596 "Zend/zend_language_scanner.l"
+#line 1680 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_LOGICAL_XOR);
 }
-#line 4661 "Zend/zend_language_scanner.c"
+#line 4785 "Zend/zend_language_scanner.c"
 yy372:
                YYDEBUG(372, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4871,11 +4995,11 @@ yy401:
                }
                YYDEBUG(402, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1272 "Zend/zend_language_scanner.l"
+#line 1356 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CASE);
 }
-#line 4879 "Zend/zend_language_scanner.c"
+#line 5003 "Zend/zend_language_scanner.c"
 yy403:
                YYDEBUG(403, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -4926,11 +5050,11 @@ yy410:
                }
                YYDEBUG(411, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1292 "Zend/zend_language_scanner.l"
+#line 1376 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ECHO);
 }
-#line 4934 "Zend/zend_language_scanner.c"
+#line 5058 "Zend/zend_language_scanner.c"
 yy412:
                YYDEBUG(412, *YYCURSOR);
                ++YYCURSOR;
@@ -4954,11 +5078,11 @@ yy412:
 yy413:
                YYDEBUG(413, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1216 "Zend/zend_language_scanner.l"
+#line 1300 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ELSE);
 }
-#line 4962 "Zend/zend_language_scanner.c"
+#line 5086 "Zend/zend_language_scanner.c"
 yy414:
                YYDEBUG(414, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5003,11 +5127,11 @@ yy420:
                }
                YYDEBUG(421, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1400 "Zend/zend_language_scanner.l"
+#line 1484 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_EVAL);
 }
-#line 5011 "Zend/zend_language_scanner.c"
+#line 5135 "Zend/zend_language_scanner.c"
 yy422:
                YYDEBUG(422, *YYCURSOR);
                ++YYCURSOR;
@@ -5016,11 +5140,11 @@ yy422:
                }
                YYDEBUG(423, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1158 "Zend/zend_language_scanner.l"
+#line 1242 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_EXIT);
 }
-#line 5024 "Zend/zend_language_scanner.c"
+#line 5148 "Zend/zend_language_scanner.c"
 yy424:
                YYDEBUG(424, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5059,11 +5183,11 @@ yy429:
                }
                YYDEBUG(430, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1288 "Zend/zend_language_scanner.l"
+#line 1372 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_GOTO);
 }
-#line 5067 "Zend/zend_language_scanner.c"
+#line 5191 "Zend/zend_language_scanner.c"
 yy431:
                YYDEBUG(431, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5112,11 +5236,11 @@ yy436:
                }
                YYDEBUG(437, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1480 "Zend/zend_language_scanner.l"
+#line 1564 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_LIST);
 }
-#line 5120 "Zend/zend_language_scanner.c"
+#line 5244 "Zend/zend_language_scanner.c"
 yy438:
                YYDEBUG(438, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5303,11 +5427,11 @@ yy467:
                ++YYCURSOR;
                YYDEBUG(469, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1372 "Zend/zend_language_scanner.l"
+#line 1456 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_INT_CAST);
 }
-#line 5311 "Zend/zend_language_scanner.c"
+#line 5435 "Zend/zend_language_scanner.c"
 yy470:
                YYDEBUG(470, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5404,11 +5528,13 @@ yy480:
 yy481:
                YYDEBUG(481, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2165 "Zend/zend_language_scanner.l"
+#line 2249 "Zend/zend_language_scanner.l"
                {
        char *s;
-       int bprefix = (yytext[0] != '<') ? 1 : 0;
+       unsigned char *saved_cursor;
+       int bprefix = (yytext[0] != '<') ? 1 : 0, spacing = 0, indentation = 0;
        zend_heredoc_label *heredoc_label = emalloc(sizeof(zend_heredoc_label));
+       zend_bool is_heredoc = 1;
 
        CG(zend_lineno)++;
        heredoc_label->length = yyleng-bprefix-3-1-(yytext[yyleng-2]=='\r'?1:0);
@@ -5421,6 +5547,7 @@ yy481:
        if (*s == '\'') {
                s++;
                heredoc_label->length -= 2;
+               is_heredoc = 0;
 
                BEGIN(ST_NOWDOC);
        } else {
@@ -5433,25 +5560,105 @@ yy481:
        }
 
        heredoc_label->label = estrndup(s, heredoc_label->length);
+       heredoc_label->indentation = 0;
+       saved_cursor = YYCURSOR;
+
+       zend_ptr_stack_push(&SCNG(heredoc_label_stack), (void *) heredoc_label);
+
+       while (YYCURSOR < YYLIMIT && (*YYCURSOR == ' ' || *YYCURSOR == '\t')) {
+               if (*YYCURSOR == '\t') {
+                       spacing |= HEREDOC_USING_TABS;
+               } else {
+                       spacing |= HEREDOC_USING_SPACES;
+               }
+               ++YYCURSOR;
+               ++indentation;
+       }
+
+       if (YYCURSOR == YYLIMIT) {
+               YYCURSOR = saved_cursor;
+               RETURN_TOKEN(T_START_HEREDOC);
+       }
 
        /* Check for ending label on the next line */
        if (heredoc_label->length < YYLIMIT - YYCURSOR && !memcmp(YYCURSOR, s, heredoc_label->length)) {
-               YYCTYPE *end = YYCURSOR + heredoc_label->length;
+               if (!IS_LABEL_START(YYCURSOR[heredoc_label->length])) {
+                       if (spacing == (HEREDOC_USING_SPACES | HEREDOC_USING_TABS)) {
+                               zend_throw_exception(zend_ce_parse_error, "Invalid indentation - tabs and spaces cannot be mixed", 0);
+                       }
 
-               if (*end == ';') {
-                       end++;
-               }
+                       YYCURSOR = saved_cursor;
+                       heredoc_label->indentation = indentation;
 
-               if (*end == '\n' || *end == '\r') {
                        BEGIN(ST_END_HEREDOC);
+                       RETURN_TOKEN(T_START_HEREDOC);
                }
        }
 
-       zend_ptr_stack_push(&SCNG(heredoc_label_stack), (void *) heredoc_label);
+       YYCURSOR = saved_cursor;
+
+       if (is_heredoc && !SCNG(heredoc_scan_ahead)) {
+               zend_lex_state current_state;
+               int heredoc_nesting_level = 1;
+               int first_token = 0;
+
+               zend_save_lexical_state(&current_state);
+
+               SCNG(heredoc_scan_ahead) = 1;
+               SCNG(heredoc_indentation) = 0;
+               SCNG(heredoc_indentation_uses_spaces) = 0;
+               LANG_SCNG(on_event) = NULL;
+
+               zend_ptr_stack_reverse_apply(&current_state.heredoc_label_stack, copy_heredoc_label_stack);
+
+               while (heredoc_nesting_level) {
+                       zval zv;
+                       int retval;
+
+                       ZVAL_UNDEF(&zv);
+                       retval = lex_scan(&zv, NULL);
+                       zval_dtor(&zv);
+
+                       if (EG(exception)) {
+                               zend_clear_exception();
+                               break;
+                       }
+
+                       if (!first_token) {
+                               first_token = retval;
+                       }
+
+                       switch (retval) {
+                               case T_START_HEREDOC:
+                                       ++heredoc_nesting_level;
+                                       break;
+                               case T_END_HEREDOC:
+                                       --heredoc_nesting_level;
+                                       break;
+                               case END:
+                                       heredoc_nesting_level = 0;
+                       }
+               }
+
+               if (
+                   (first_token == T_VARIABLE
+                    || first_token == T_DOLLAR_OPEN_CURLY_BRACES
+                    || first_token == T_CURLY_OPEN
+                   ) && SCNG(heredoc_indentation)) {
+                       zend_throw_exception_ex(zend_ce_parse_error, 0, "Invalid body indentation level (expecting an indentation level of at least %d)", SCNG(heredoc_indentation));
+               }
+
+               heredoc_label->indentation = SCNG(heredoc_indentation);
+               heredoc_label->indentation_uses_spaces = SCNG(heredoc_indentation_uses_spaces);
+
+               zend_restore_lexical_state(&current_state);
+               SCNG(heredoc_scan_ahead) = 0;
+               CG(increment_lineno) = 0;
+       }
 
        RETURN_TOKEN(T_START_HEREDOC);
 }
-#line 5455 "Zend/zend_language_scanner.c"
+#line 5662 "Zend/zend_language_scanner.c"
 yy482:
                YYDEBUG(482, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5471,11 +5678,11 @@ yy484:
                }
                YYDEBUG(485, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1484 "Zend/zend_language_scanner.l"
+#line 1568 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ARRAY);
 }
-#line 5479 "Zend/zend_language_scanner.c"
+#line 5686 "Zend/zend_language_scanner.c"
 yy486:
                YYDEBUG(486, *YYCURSOR);
                ++YYCURSOR;
@@ -5484,11 +5691,11 @@ yy486:
                }
                YYDEBUG(487, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1280 "Zend/zend_language_scanner.l"
+#line 1364 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_BREAK);
 }
-#line 5492 "Zend/zend_language_scanner.c"
+#line 5699 "Zend/zend_language_scanner.c"
 yy488:
                YYDEBUG(488, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5503,11 +5710,11 @@ yy489:
                }
                YYDEBUG(490, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1192 "Zend/zend_language_scanner.l"
+#line 1276 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CATCH);
 }
-#line 5511 "Zend/zend_language_scanner.c"
+#line 5718 "Zend/zend_language_scanner.c"
 yy491:
                YYDEBUG(491, *YYCURSOR);
                ++YYCURSOR;
@@ -5516,11 +5723,11 @@ yy491:
                }
                YYDEBUG(492, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1300 "Zend/zend_language_scanner.l"
+#line 1384 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CLASS);
 }
-#line 5524 "Zend/zend_language_scanner.c"
+#line 5731 "Zend/zend_language_scanner.c"
 yy493:
                YYDEBUG(493, *YYCURSOR);
                ++YYCURSOR;
@@ -5529,11 +5736,11 @@ yy493:
                }
                YYDEBUG(494, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1364 "Zend/zend_language_scanner.l"
+#line 1448 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CLONE);
 }
-#line 5537 "Zend/zend_language_scanner.c"
+#line 5744 "Zend/zend_language_scanner.c"
 yy495:
                YYDEBUG(495, *YYCURSOR);
                ++YYCURSOR;
@@ -5542,11 +5749,11 @@ yy495:
                }
                YYDEBUG(496, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1170 "Zend/zend_language_scanner.l"
+#line 1254 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CONST);
 }
-#line 5550 "Zend/zend_language_scanner.c"
+#line 5757 "Zend/zend_language_scanner.c"
 yy497:
                YYDEBUG(497, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5579,11 +5786,11 @@ yy501:
                }
                YYDEBUG(502, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1440 "Zend/zend_language_scanner.l"
+#line 1524 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_EMPTY);
 }
-#line 5587 "Zend/zend_language_scanner.c"
+#line 5794 "Zend/zend_language_scanner.c"
 yy503:
                YYDEBUG(503, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5604,11 +5811,11 @@ yy505:
                }
                YYDEBUG(506, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1212 "Zend/zend_language_scanner.l"
+#line 1296 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ENDIF);
 }
-#line 5612 "Zend/zend_language_scanner.c"
+#line 5819 "Zend/zend_language_scanner.c"
 yy507:
                YYDEBUG(507, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5650,11 +5857,11 @@ yy510:
 yy511:
                YYDEBUG(511, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1456 "Zend/zend_language_scanner.l"
+#line 1540 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_FINAL);
 }
-#line 5658 "Zend/zend_language_scanner.c"
+#line 5865 "Zend/zend_language_scanner.c"
 yy512:
                YYDEBUG(512, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5711,11 +5918,11 @@ yy520:
                }
                YYDEBUG(521, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1436 "Zend/zend_language_scanner.l"
+#line 1520 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ISSET);
 }
-#line 5719 "Zend/zend_language_scanner.c"
+#line 5926 "Zend/zend_language_scanner.c"
 yy522:
                YYDEBUG(522, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5730,11 +5937,11 @@ yy523:
                }
                YYDEBUG(524, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1296 "Zend/zend_language_scanner.l"
+#line 1380 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_PRINT);
 }
-#line 5738 "Zend/zend_language_scanner.c"
+#line 5945 "Zend/zend_language_scanner.c"
 yy525:
                YYDEBUG(525, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5785,11 +5992,11 @@ yy532:
                }
                YYDEBUG(533, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1200 "Zend/zend_language_scanner.l"
+#line 1284 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_THROW);
 }
-#line 5793 "Zend/zend_language_scanner.c"
+#line 6000 "Zend/zend_language_scanner.c"
 yy534:
                YYDEBUG(534, *YYCURSOR);
                ++YYCURSOR;
@@ -5798,11 +6005,11 @@ yy534:
                }
                YYDEBUG(535, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1308 "Zend/zend_language_scanner.l"
+#line 1392 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_TRAIT);
 }
-#line 5806 "Zend/zend_language_scanner.c"
+#line 6013 "Zend/zend_language_scanner.c"
 yy536:
                YYDEBUG(536, *YYCURSOR);
                ++YYCURSOR;
@@ -5811,11 +6018,11 @@ yy536:
                }
                YYDEBUG(537, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1472 "Zend/zend_language_scanner.l"
+#line 1556 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_UNSET);
 }
-#line 5819 "Zend/zend_language_scanner.c"
+#line 6026 "Zend/zend_language_scanner.c"
 yy538:
                YYDEBUG(538, *YYCURSOR);
                ++YYCURSOR;
@@ -5824,11 +6031,11 @@ yy538:
                }
                YYDEBUG(539, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1220 "Zend/zend_language_scanner.l"
+#line 1304 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_WHILE);
 }
-#line 5832 "Zend/zend_language_scanner.c"
+#line 6039 "Zend/zend_language_scanner.c"
 yy540:
                YYDEBUG(540, *YYCURSOR);
                yyaccept = 6;
@@ -5846,11 +6053,11 @@ yy540:
 yy541:
                YYDEBUG(541, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1184 "Zend/zend_language_scanner.l"
+#line 1268 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_YIELD);
 }
-#line 5854 "Zend/zend_language_scanner.c"
+#line 6061 "Zend/zend_language_scanner.c"
 yy542:
                YYDEBUG(542, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5942,11 +6149,11 @@ yy555:
                ++YYCURSOR;
                YYDEBUG(557, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1392 "Zend/zend_language_scanner.l"
+#line 1476 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_BOOL_CAST);
 }
-#line 5950 "Zend/zend_language_scanner.c"
+#line 6157 "Zend/zend_language_scanner.c"
 yy558:
                YYDEBUG(558, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -5976,11 +6183,11 @@ yy562:
                ++YYCURSOR;
                YYDEBUG(563, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1376 "Zend/zend_language_scanner.l"
+#line 1460 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_DOUBLE_CAST);
 }
-#line 5984 "Zend/zend_language_scanner.c"
+#line 6191 "Zend/zend_language_scanner.c"
 yy564:
                YYDEBUG(564, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6045,11 +6252,11 @@ yy573:
                }
                YYDEBUG(574, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1208 "Zend/zend_language_scanner.l"
+#line 1292 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ELSEIF);
 }
-#line 6053 "Zend/zend_language_scanner.c"
+#line 6260 "Zend/zend_language_scanner.c"
 yy575:
                YYDEBUG(575, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6079,11 +6286,11 @@ yy576:
 yy577:
                YYDEBUG(577, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1236 "Zend/zend_language_scanner.l"
+#line 1320 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ENDFOR);
 }
-#line 6087 "Zend/zend_language_scanner.c"
+#line 6294 "Zend/zend_language_scanner.c"
 yy578:
                YYDEBUG(578, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6128,11 +6335,11 @@ yy584:
                }
                YYDEBUG(585, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1432 "Zend/zend_language_scanner.l"
+#line 1516 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_GLOBAL);
 }
-#line 6136 "Zend/zend_language_scanner.c"
+#line 6343 "Zend/zend_language_scanner.c"
 yy586:
                YYDEBUG(586, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6189,11 +6396,11 @@ yy594:
                }
                YYDEBUG(595, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1468 "Zend/zend_language_scanner.l"
+#line 1552 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_PUBLIC);
 }
-#line 6197 "Zend/zend_language_scanner.c"
+#line 6404 "Zend/zend_language_scanner.c"
 yy596:
                YYDEBUG(596, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6208,11 +6415,11 @@ yy597:
                }
                YYDEBUG(598, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1174 "Zend/zend_language_scanner.l"
+#line 1258 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_RETURN);
 }
-#line 6216 "Zend/zend_language_scanner.c"
+#line 6423 "Zend/zend_language_scanner.c"
 yy599:
                YYDEBUG(599, *YYCURSOR);
                ++YYCURSOR;
@@ -6221,11 +6428,11 @@ yy599:
                }
                YYDEBUG(600, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1448 "Zend/zend_language_scanner.l"
+#line 1532 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_STATIC);
 }
-#line 6229 "Zend/zend_language_scanner.c"
+#line 6436 "Zend/zend_language_scanner.c"
 yy601:
                YYDEBUG(601, *YYCURSOR);
                ++YYCURSOR;
@@ -6234,11 +6441,11 @@ yy601:
                }
                YYDEBUG(602, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1264 "Zend/zend_language_scanner.l"
+#line 1348 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_SWITCH);
 }
-#line 6242 "Zend/zend_language_scanner.c"
+#line 6449 "Zend/zend_language_scanner.c"
 yy603:
                YYDEBUG(603, *YYCURSOR);
                ++YYCURSOR;
@@ -6318,11 +6525,11 @@ yy614:
                ++YYCURSOR;
                YYDEBUG(615, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1384 "Zend/zend_language_scanner.l"
+#line 1468 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ARRAY_CAST);
 }
-#line 6326 "Zend/zend_language_scanner.c"
+#line 6533 "Zend/zend_language_scanner.c"
 yy616:
                YYDEBUG(616, *YYCURSOR);
                ++YYCURSOR;
@@ -6368,11 +6575,11 @@ yy622:
                ++YYCURSOR;
                YYDEBUG(623, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1396 "Zend/zend_language_scanner.l"
+#line 1480 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_UNSET_CAST);
 }
-#line 6376 "Zend/zend_language_scanner.c"
+#line 6583 "Zend/zend_language_scanner.c"
 yy624:
                YYDEBUG(624, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6399,11 +6606,11 @@ yy627:
                }
                YYDEBUG(628, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1248 "Zend/zend_language_scanner.l"
+#line 1332 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_DECLARE);
 }
-#line 6407 "Zend/zend_language_scanner.c"
+#line 6614 "Zend/zend_language_scanner.c"
 yy629:
                YYDEBUG(629, *YYCURSOR);
                ++YYCURSOR;
@@ -6412,11 +6619,11 @@ yy629:
                }
                YYDEBUG(630, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1276 "Zend/zend_language_scanner.l"
+#line 1360 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_DEFAULT);
 }
-#line 6420 "Zend/zend_language_scanner.c"
+#line 6627 "Zend/zend_language_scanner.c"
 yy631:
                YYDEBUG(631, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6449,11 +6656,11 @@ yy635:
                }
                YYDEBUG(636, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1312 "Zend/zend_language_scanner.l"
+#line 1396 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_EXTENDS);
 }
-#line 6457 "Zend/zend_language_scanner.c"
+#line 6664 "Zend/zend_language_scanner.c"
 yy637:
                YYDEBUG(637, *YYCURSOR);
                ++YYCURSOR;
@@ -6462,11 +6669,11 @@ yy637:
                }
                YYDEBUG(638, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1196 "Zend/zend_language_scanner.l"
+#line 1280 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_FINALLY);
 }
-#line 6470 "Zend/zend_language_scanner.c"
+#line 6677 "Zend/zend_language_scanner.c"
 yy639:
                YYDEBUG(639, *YYCURSOR);
                ++YYCURSOR;
@@ -6475,11 +6682,11 @@ yy639:
                }
                YYDEBUG(640, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1240 "Zend/zend_language_scanner.l"
+#line 1324 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_FOREACH);
 }
-#line 6483 "Zend/zend_language_scanner.c"
+#line 6690 "Zend/zend_language_scanner.c"
 yy641:
                YYDEBUG(641, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6513,11 +6720,11 @@ yy643:
 yy644:
                YYDEBUG(644, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1404 "Zend/zend_language_scanner.l"
+#line 1488 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_INCLUDE);
 }
-#line 6521 "Zend/zend_language_scanner.c"
+#line 6728 "Zend/zend_language_scanner.c"
 yy645:
                YYDEBUG(645, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6550,11 +6757,11 @@ yy649:
                }
                YYDEBUG(650, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1460 "Zend/zend_language_scanner.l"
+#line 1544 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_PRIVATE);
 }
-#line 6558 "Zend/zend_language_scanner.c"
+#line 6765 "Zend/zend_language_scanner.c"
 yy651:
                YYDEBUG(651, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6582,11 +6789,11 @@ yy652:
 yy653:
                YYDEBUG(653, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1412 "Zend/zend_language_scanner.l"
+#line 1496 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_REQUIRE);
 }
-#line 6590 "Zend/zend_language_scanner.c"
+#line 6797 "Zend/zend_language_scanner.c"
 yy654:
                YYDEBUG(654, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6606,11 +6813,11 @@ yy656:
                }
                YYDEBUG(657, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1814 "Zend/zend_language_scanner.l"
+#line 1898 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_DIR);
 }
-#line 6614 "Zend/zend_language_scanner.c"
+#line 6821 "Zend/zend_language_scanner.c"
 yy658:
                YYDEBUG(658, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6655,21 +6862,21 @@ yy665:
                ++YYCURSOR;
                YYDEBUG(666, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1380 "Zend/zend_language_scanner.l"
+#line 1464 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_STRING_CAST);
 }
-#line 6663 "Zend/zend_language_scanner.c"
+#line 6870 "Zend/zend_language_scanner.c"
 yy667:
                YYDEBUG(667, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(668, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1388 "Zend/zend_language_scanner.l"
+#line 1472 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_OBJECT_CAST);
 }
-#line 6673 "Zend/zend_language_scanner.c"
+#line 6880 "Zend/zend_language_scanner.c"
 yy669:
                YYDEBUG(669, *YYCURSOR);
                ++YYCURSOR;
@@ -6678,11 +6885,11 @@ yy669:
                }
                YYDEBUG(670, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1452 "Zend/zend_language_scanner.l"
+#line 1536 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ABSTRACT);
 }
-#line 6686 "Zend/zend_language_scanner.c"
+#line 6893 "Zend/zend_language_scanner.c"
 yy671:
                YYDEBUG(671, *YYCURSOR);
                ++YYCURSOR;
@@ -6691,11 +6898,11 @@ yy671:
                }
                YYDEBUG(672, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1488 "Zend/zend_language_scanner.l"
+#line 1572 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CALLABLE);
 }
-#line 6699 "Zend/zend_language_scanner.c"
+#line 6906 "Zend/zend_language_scanner.c"
 yy673:
                YYDEBUG(673, *YYCURSOR);
                ++YYCURSOR;
@@ -6704,11 +6911,11 @@ yy673:
                }
                YYDEBUG(674, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1284 "Zend/zend_language_scanner.l"
+#line 1368 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CONTINUE);
 }
-#line 6712 "Zend/zend_language_scanner.c"
+#line 6919 "Zend/zend_language_scanner.c"
 yy675:
                YYDEBUG(675, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6735,11 +6942,11 @@ yy678:
                }
                YYDEBUG(679, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1224 "Zend/zend_language_scanner.l"
+#line 1308 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ENDWHILE);
 }
-#line 6743 "Zend/zend_language_scanner.c"
+#line 6950 "Zend/zend_language_scanner.c"
 yy680:
                YYDEBUG(680, *YYCURSOR);
                ++YYCURSOR;
@@ -6748,11 +6955,11 @@ yy680:
                }
                YYDEBUG(681, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1166 "Zend/zend_language_scanner.l"
+#line 1250 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_FUNCTION);
 }
-#line 6756 "Zend/zend_language_scanner.c"
+#line 6963 "Zend/zend_language_scanner.c"
 yy682:
                YYDEBUG(682, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6820,11 +7027,11 @@ yy692:
                }
                YYDEBUG(693, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1810 "Zend/zend_language_scanner.l"
+#line 1894 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_FILE);
 }
-#line 6828 "Zend/zend_language_scanner.c"
+#line 7035 "Zend/zend_language_scanner.c"
 yy694:
                YYDEBUG(694, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6845,11 +7052,11 @@ yy696:
                }
                YYDEBUG(697, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1806 "Zend/zend_language_scanner.l"
+#line 1890 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_LINE);
 }
-#line 6853 "Zend/zend_language_scanner.c"
+#line 7060 "Zend/zend_language_scanner.c"
 yy698:
                YYDEBUG(698, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6886,11 +7093,11 @@ yy703:
                }
                YYDEBUG(704, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1268 "Zend/zend_language_scanner.l"
+#line 1352 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ENDSWITCH);
 }
-#line 6894 "Zend/zend_language_scanner.c"
+#line 7101 "Zend/zend_language_scanner.c"
 yy705:
                YYDEBUG(705, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6917,11 +7124,11 @@ yy708:
                }
                YYDEBUG(709, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1428 "Zend/zend_language_scanner.l"
+#line 1512 "Zend/zend_language_scanner.l"
                {
     RETURN_TOKEN(T_INSTEADOF);
 }
-#line 6925 "Zend/zend_language_scanner.c"
+#line 7132 "Zend/zend_language_scanner.c"
 yy710:
                YYDEBUG(710, *YYCURSOR);
                ++YYCURSOR;
@@ -6930,11 +7137,11 @@ yy710:
                }
                YYDEBUG(711, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1304 "Zend/zend_language_scanner.l"
+#line 1388 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_INTERFACE);
 }
-#line 6938 "Zend/zend_language_scanner.c"
+#line 7145 "Zend/zend_language_scanner.c"
 yy712:
                YYDEBUG(712, *YYCURSOR);
                ++YYCURSOR;
@@ -6943,11 +7150,11 @@ yy712:
                }
                YYDEBUG(713, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1420 "Zend/zend_language_scanner.l"
+#line 1504 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_NAMESPACE);
 }
-#line 6951 "Zend/zend_language_scanner.c"
+#line 7158 "Zend/zend_language_scanner.c"
 yy714:
                YYDEBUG(714, *YYCURSOR);
                ++YYCURSOR;
@@ -6956,11 +7163,11 @@ yy714:
                }
                YYDEBUG(715, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1464 "Zend/zend_language_scanner.l"
+#line 1548 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_PROTECTED);
 }
-#line 6964 "Zend/zend_language_scanner.c"
+#line 7171 "Zend/zend_language_scanner.c"
 yy716:
                YYDEBUG(716, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -6981,11 +7188,11 @@ yy718:
                }
                YYDEBUG(719, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1790 "Zend/zend_language_scanner.l"
+#line 1874 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_CLASS_C);
 }
-#line 6989 "Zend/zend_language_scanner.c"
+#line 7196 "Zend/zend_language_scanner.c"
 yy720:
                YYDEBUG(720, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7017,11 +7224,11 @@ yy724:
                }
                YYDEBUG(725, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1794 "Zend/zend_language_scanner.l"
+#line 1878 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_TRAIT_C);
 }
-#line 7025 "Zend/zend_language_scanner.c"
+#line 7232 "Zend/zend_language_scanner.c"
 yy726:
                YYDEBUG(726, *YYCURSOR);
                ++YYCURSOR;
@@ -7030,11 +7237,11 @@ yy726:
                }
                YYDEBUG(727, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1252 "Zend/zend_language_scanner.l"
+#line 1336 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ENDDECLARE);
 }
-#line 7038 "Zend/zend_language_scanner.c"
+#line 7245 "Zend/zend_language_scanner.c"
 yy728:
                YYDEBUG(728, *YYCURSOR);
                ++YYCURSOR;
@@ -7043,11 +7250,11 @@ yy728:
                }
                YYDEBUG(729, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1244 "Zend/zend_language_scanner.l"
+#line 1328 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_ENDFOREACH);
 }
-#line 7051 "Zend/zend_language_scanner.c"
+#line 7258 "Zend/zend_language_scanner.c"
 yy730:
                YYDEBUG(730, *YYCURSOR);
                ++YYCURSOR;
@@ -7056,11 +7263,11 @@ yy730:
                }
                YYDEBUG(731, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1316 "Zend/zend_language_scanner.l"
+#line 1400 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_IMPLEMENTS);
 }
-#line 7064 "Zend/zend_language_scanner.c"
+#line 7271 "Zend/zend_language_scanner.c"
 yy732:
                YYDEBUG(732, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7075,11 +7282,11 @@ yy733:
                }
                YYDEBUG(734, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1256 "Zend/zend_language_scanner.l"
+#line 1340 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_INSTANCEOF);
 }
-#line 7083 "Zend/zend_language_scanner.c"
+#line 7290 "Zend/zend_language_scanner.c"
 yy735:
                YYDEBUG(735, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7127,11 +7334,11 @@ yy739:
                }
                YYDEBUG(740, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1802 "Zend/zend_language_scanner.l"
+#line 1886 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_METHOD_C);
 }
-#line 7135 "Zend/zend_language_scanner.c"
+#line 7342 "Zend/zend_language_scanner.c"
 yy741:
                YYDEBUG(741, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7155,13 +7362,13 @@ yy744:
                ++YYCURSOR;
                YYDEBUG(745, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1178 "Zend/zend_language_scanner.l"
+#line 1262 "Zend/zend_language_scanner.l"
                {
        yyless(yyleng - 1);
        HANDLE_NEWLINES(yytext, yyleng);
        RETURN_TOKEN(T_YIELD_FROM);
 }
-#line 7165 "Zend/zend_language_scanner.c"
+#line 7372 "Zend/zend_language_scanner.c"
 yy746:
                YYDEBUG(746, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7186,11 +7393,11 @@ yy749:
                }
                YYDEBUG(750, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1408 "Zend/zend_language_scanner.l"
+#line 1492 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_INCLUDE_ONCE);
 }
-#line 7194 "Zend/zend_language_scanner.c"
+#line 7401 "Zend/zend_language_scanner.c"
 yy751:
                YYDEBUG(751, *YYCURSOR);
                ++YYCURSOR;
@@ -7199,11 +7406,11 @@ yy751:
                }
                YYDEBUG(752, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1416 "Zend/zend_language_scanner.l"
+#line 1500 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_REQUIRE_ONCE);
 }
-#line 7207 "Zend/zend_language_scanner.c"
+#line 7414 "Zend/zend_language_scanner.c"
 yy753:
                YYDEBUG(753, *YYCURSOR);
                ++YYCURSOR;
@@ -7212,11 +7419,11 @@ yy753:
                }
                YYDEBUG(754, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1798 "Zend/zend_language_scanner.l"
+#line 1882 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_FUNC_C);
 }
-#line 7220 "Zend/zend_language_scanner.c"
+#line 7427 "Zend/zend_language_scanner.c"
 yy755:
                YYDEBUG(755, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7242,11 +7449,11 @@ yy758:
                }
                YYDEBUG(759, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1818 "Zend/zend_language_scanner.l"
+#line 1902 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_NS_C);
 }
-#line 7250 "Zend/zend_language_scanner.c"
+#line 7457 "Zend/zend_language_scanner.c"
 yy760:
                YYDEBUG(760, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7260,11 +7467,11 @@ yy761:
                }
                YYDEBUG(762, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1444 "Zend/zend_language_scanner.l"
+#line 1528 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_HALT_COMPILER);
 }
-#line 7268 "Zend/zend_language_scanner.c"
+#line 7475 "Zend/zend_language_scanner.c"
        }
 /* *********************************** */
 yyc_ST_LOOKING_FOR_PROPERTY:
@@ -7330,13 +7537,13 @@ yy765:
 yy766:
                YYDEBUG(766, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1338 "Zend/zend_language_scanner.l"
+#line 1422 "Zend/zend_language_scanner.l"
                {
        yyless(0);
        yy_pop_state();
        goto restart;
 }
-#line 7340 "Zend/zend_language_scanner.c"
+#line 7547 "Zend/zend_language_scanner.c"
 yy767:
                YYDEBUG(767, *YYCURSOR);
                ++YYCURSOR;
@@ -7348,11 +7555,11 @@ yy767:
                }
                YYDEBUG(769, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1325 "Zend/zend_language_scanner.l"
+#line 1409 "Zend/zend_language_scanner.l"
                {
        goto return_whitespace;
 }
-#line 7356 "Zend/zend_language_scanner.c"
+#line 7563 "Zend/zend_language_scanner.c"
 yy770:
                YYDEBUG(770, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7369,22 +7576,22 @@ yy771:
                }
                YYDEBUG(773, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1333 "Zend/zend_language_scanner.l"
+#line 1417 "Zend/zend_language_scanner.l"
                {
        yy_pop_state();
        RETURN_TOKEN_WITH_STR(T_STRING, 0);
 }
-#line 7378 "Zend/zend_language_scanner.c"
+#line 7585 "Zend/zend_language_scanner.c"
 yy774:
                YYDEBUG(774, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(775, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1329 "Zend/zend_language_scanner.l"
+#line 1413 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN(T_OBJECT_OPERATOR);
 }
-#line 7388 "Zend/zend_language_scanner.c"
+#line 7595 "Zend/zend_language_scanner.c"
        }
 /* *********************************** */
 yyc_ST_LOOKING_FOR_VARNAME:
@@ -7441,14 +7648,14 @@ yy778:
 yy779:
                YYDEBUG(779, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1642 "Zend/zend_language_scanner.l"
+#line 1726 "Zend/zend_language_scanner.l"
                {
        yyless(0);
        yy_pop_state();
        yy_push_state(ST_IN_SCRIPTING);
        goto restart;
 }
-#line 7452 "Zend/zend_language_scanner.c"
+#line 7659 "Zend/zend_language_scanner.c"
 yy780:
                YYDEBUG(780, *YYCURSOR);
                yych = *(YYMARKER = ++YYCURSOR);
@@ -7495,14 +7702,14 @@ yy784:
                ++YYCURSOR;
                YYDEBUG(785, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1634 "Zend/zend_language_scanner.l"
+#line 1718 "Zend/zend_language_scanner.l"
                {
        yyless(yyleng - 1);
        yy_pop_state();
        yy_push_state(ST_IN_SCRIPTING);
        RETURN_TOKEN_WITH_STR(T_STRING_VARNAME, 0);
 }
-#line 7506 "Zend/zend_language_scanner.c"
+#line 7713 "Zend/zend_language_scanner.c"
        }
 /* *********************************** */
 yyc_ST_NOWDOC:
@@ -7513,11 +7720,10 @@ yyc_ST_NOWDOC:
        ++YYCURSOR;
        YYDEBUG(789, *YYCURSOR);
        yyleng = YYCURSOR - SCNG(yy_text);
-#line 2429 "Zend/zend_language_scanner.l"
+#line 2636 "Zend/zend_language_scanner.l"
        {
-       int newline = 0;
-
        zend_heredoc_label *heredoc_label = zend_ptr_stack_top(&SCNG(heredoc_label_stack));
+       int newline = 0, indentation = 0, spacing = -1;
 
        if (YYCURSOR > YYLIMIT) {
                RETURN_TOKEN(END);
@@ -7533,28 +7739,51 @@ yyc_ST_NOWDOC:
                                }
                                /* fall through */
                        case '\n':
+                               indentation = spacing = 0;
+
+                               while (YYCURSOR < YYLIMIT && (*YYCURSOR == ' ' || *YYCURSOR == '\t')) {
+                                       if (*YYCURSOR == '\t') {
+                                               spacing |= HEREDOC_USING_TABS;
+                                       } else {
+                                               spacing |= HEREDOC_USING_SPACES;
+                                       }
+                                       ++YYCURSOR;
+                                       ++indentation;
+                               }
+
+                               if (YYCURSOR == YYLIMIT) {
+                                       yyleng = YYCURSOR - SCNG(yy_text);
+                                       HANDLE_NEWLINES(yytext, yyleng);
+                                       ZVAL_NULL(zendlval);
+                                       RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
+                               }
+
                                /* Check for ending label on the next line */
                                if (IS_LABEL_START(*YYCURSOR) && heredoc_label->length < YYLIMIT - YYCURSOR && !memcmp(YYCURSOR, heredoc_label->label, heredoc_label->length)) {
-                                       YYCTYPE *end = YYCURSOR + heredoc_label->length;
+                                       if (IS_LABEL_START(YYCURSOR[heredoc_label->length])) {
+                                               continue;
+                                       }
 
-                                       if (*end == ';') {
-                                               end++;
+                                       if (spacing == (HEREDOC_USING_SPACES | HEREDOC_USING_TABS)) {
+                                               zend_throw_exception(zend_ce_parse_error, "Invalid indentation - tabs and spaces cannot be mixed", 0);
                                        }
 
-                                       if (*end == '\n' || *end == '\r') {
-                                               /* newline before label will be subtracted from returned text, but
-                                                * yyleng/yytext will include it, for zend_highlight/strip, tokenizer, etc. */
-                                               if (YYCURSOR[-2] == '\r' && YYCURSOR[-1] == '\n') {
-                                                       newline = 2; /* Windows newline */
-                                               } else {
-                                                       newline = 1;
-                                               }
+                                       /* newline before label will be subtracted from returned text, but
+                                        * yyleng/yytext will include it, for zend_highlight/strip, tokenizer, etc. */
+                                       if (YYCURSOR[-indentation - 2] == '\r' && YYCURSOR[-indentation - 1] == '\n') {
+                                               newline = 2; /* Windows newline */
+                                       } else {
+                                               newline = 1;
+                                       }
 
-                                               CG(increment_lineno) = 1; /* For newline before label */
-                                               BEGIN(ST_END_HEREDOC);
+                                       CG(increment_lineno) = 1; /* For newline before label */
 
-                                               goto nowdoc_scan_done;
-                                       }
+                                       YYCURSOR -= indentation;
+                                       heredoc_label->indentation = indentation;
+
+                                       BEGIN(ST_END_HEREDOC);
+
+                                       goto nowdoc_scan_done;
                                }
                                /* fall through */
                        default:
@@ -7564,12 +7793,17 @@ yyc_ST_NOWDOC:
 
 nowdoc_scan_done:
        yyleng = YYCURSOR - SCNG(yy_text);
+       ZVAL_STRINGL(zendlval, yytext, yyleng);
+
+       if (!EG(exception) && spacing != -1 && PARSER_MODE()
+           && !strip_multiline_string_indentation(zendlval, newline, indentation, spacing == HEREDOC_USING_SPACES)) {
+               RETURN_TOKEN(T_ERROR);
+       }
 
-       zend_copy_value(zendlval, yytext, yyleng - newline);
        HANDLE_NEWLINES(yytext, yyleng - newline);
        RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
 }
-#line 7573 "Zend/zend_language_scanner.c"
+#line 7807 "Zend/zend_language_scanner.c"
 /* *********************************** */
 yyc_ST_VAR_OFFSET:
        {
@@ -7657,7 +7891,7 @@ yy792:
                ++YYCURSOR;
                YYDEBUG(793, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 2486 "Zend/zend_language_scanner.l"
+#line 2720 "Zend/zend_language_scanner.l"
                {
        if (YYCURSOR > YYLIMIT) {
                RETURN_TOKEN(END);
@@ -7666,13 +7900,13 @@ yy792:
        zend_error(E_COMPILE_WARNING,"Unexpected character in input:  '%c' (ASCII=%d) state=%d", yytext[0], yytext[0], YYSTATE);
        goto restart;
 }
-#line 7670 "Zend/zend_language_scanner.c"
+#line 7904 "Zend/zend_language_scanner.c"
 yy794:
                YYDEBUG(794, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(795, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1933 "Zend/zend_language_scanner.l"
+#line 2017 "Zend/zend_language_scanner.l"
                {
        /* Invalid rule to return a more explicit parse error with proper line number */
        yyless(0);
@@ -7680,19 +7914,19 @@ yy794:
        ZVAL_NULL(zendlval);
        RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
 }
-#line 7684 "Zend/zend_language_scanner.c"
+#line 7918 "Zend/zend_language_scanner.c"
 yy796:
                YYDEBUG(796, *YYCURSOR);
                ++YYCURSOR;
 yy797:
                YYDEBUG(797, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1928 "Zend/zend_language_scanner.l"
+#line 2012 "Zend/zend_language_scanner.l"
                {
        /* Only '[' or '-' can be valid, but returning other tokens will allow a more explicit parse error */
        RETURN_TOKEN(yytext[0]);
 }
-#line 7696 "Zend/zend_language_scanner.c"
+#line 7930 "Zend/zend_language_scanner.c"
 yy798:
                YYDEBUG(798, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7727,7 +7961,7 @@ yy799:
 yy800:
                YYDEBUG(800, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1756 "Zend/zend_language_scanner.l"
+#line 1840 "Zend/zend_language_scanner.l"
                { /* Offset could be treated as a long */
        if (yyleng < MAX_LENGTH_OF_LONG - 1 || (yyleng == MAX_LENGTH_OF_LONG - 1 && strcmp(yytext, long_min_digits) < 0)) {
                char *end;
@@ -7743,7 +7977,7 @@ string:
        }
        RETURN_TOKEN_WITH_VAL(T_NUM_STRING);
 }
-#line 7747 "Zend/zend_language_scanner.c"
+#line 7981 "Zend/zend_language_scanner.c"
 yy801:
                YYDEBUG(801, *YYCURSOR);
                ++YYCURSOR;
@@ -7765,22 +7999,22 @@ yy803:
                }
                YYDEBUG(805, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1941 "Zend/zend_language_scanner.l"
+#line 2025 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN_WITH_STR(T_STRING, 0);
 }
-#line 7773 "Zend/zend_language_scanner.c"
+#line 8007 "Zend/zend_language_scanner.c"
 yy806:
                YYDEBUG(806, *YYCURSOR);
                ++YYCURSOR;
                YYDEBUG(807, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1923 "Zend/zend_language_scanner.l"
+#line 2007 "Zend/zend_language_scanner.l"
                {
        yy_pop_state();
        RETURN_TOKEN(']');
 }
-#line 7784 "Zend/zend_language_scanner.c"
+#line 8018 "Zend/zend_language_scanner.c"
 yy808:
                YYDEBUG(808, *YYCURSOR);
                ++YYCURSOR;
@@ -7805,11 +8039,11 @@ yy808:
 yy810:
                YYDEBUG(810, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1919 "Zend/zend_language_scanner.l"
+#line 2003 "Zend/zend_language_scanner.l"
                {
        RETURN_TOKEN_WITH_STR(T_VARIABLE, 1);
 }
-#line 7813 "Zend/zend_language_scanner.c"
+#line 8047 "Zend/zend_language_scanner.c"
 yy811:
                YYDEBUG(811, *YYCURSOR);
                ++YYCURSOR;
@@ -7821,7 +8055,7 @@ yy811:
 yy813:
                YYDEBUG(813, *YYCURSOR);
                yyleng = YYCURSOR - SCNG(yy_text);
-#line 1772 "Zend/zend_language_scanner.l"
+#line 1856 "Zend/zend_language_scanner.l"
                { /* Offset must be treated as a string */
        if (yyleng == 1) {
                ZVAL_INTERNED_STR(zendlval, ZSTR_CHAR((zend_uchar)*(yytext)));
@@ -7830,7 +8064,7 @@ yy813:
        }
        RETURN_TOKEN_WITH_VAL(T_NUM_STRING);
 }
-#line 7834 "Zend/zend_language_scanner.c"
+#line 8068 "Zend/zend_language_scanner.c"
 yy814:
                YYDEBUG(814, *YYCURSOR);
                yych = *++YYCURSOR;
@@ -7870,7 +8104,7 @@ yy819:
                goto yy813;
        }
 }
-#line 2495 "Zend/zend_language_scanner.l"
+#line 2729 "Zend/zend_language_scanner.l"
 
 
 emit_token_with_str:
index 2064d6d17ae079f7f6278c0d92980f47d9183cae..29263b8e71988e6e4186112097fb29aa2bf5421f 100644 (file)
@@ -61,6 +61,8 @@ typedef struct _zend_lex_state {
 typedef struct _zend_heredoc_label {
        char *label;
        int length;
+       int indentation;
+       zend_bool indentation_uses_spaces;
 } zend_heredoc_label;
 
 BEGIN_EXTERN_C()
index 5e256447f15c0742558951ae68bfc5166d353115..15afc4388733a8e0e152be4834906b7f26df75de 100644 (file)
@@ -181,6 +181,7 @@ void startup_scanner(void)
        CG(extra_fn_flags) = 0;
        zend_stack_init(&SCNG(state_stack), sizeof(int));
        zend_ptr_stack_init(&SCNG(heredoc_label_stack));
+       SCNG(heredoc_scan_ahead) = 0;
 }
 
 static void heredoc_label_dtor(zend_heredoc_label *heredoc_label) {
@@ -194,6 +195,7 @@ void shutdown_scanner(void)
        zend_stack_destroy(&SCNG(state_stack));
        zend_ptr_stack_clean(&SCNG(heredoc_label_stack), (void (*)(void *)) &heredoc_label_dtor, 1);
        zend_ptr_stack_destroy(&SCNG(heredoc_label_stack));
+       SCNG(heredoc_scan_ahead) = 0;
        SCNG(on_event) = NULL;
 }
 
@@ -1104,6 +1106,88 @@ skip_escape_conversion:
        return SUCCESS;
 }
 
+#define HEREDOC_USING_SPACES 1
+#define HEREDOC_USING_TABS 2
+
+static zend_bool strip_multiline_string_indentation(zval *zendlval, int newline, int indentation, zend_bool using_spaces)
+{
+       int len = Z_STRLEN_P(zendlval), new_len = len, i = 0, j = 0, skip, newline_count = 0;
+       char *copy = Z_STRVAL_P(zendlval);
+       zend_bool trailing_newline = 0;
+
+       while (j < len) {
+               trailing_newline = 0;
+
+               for (skip = 0; skip < indentation; ++skip, ++j, --new_len) {
+                       if (copy[j] == '\r' || copy[j] == '\n') {
+                               goto skip;
+                       }
+
+                       if (copy[j] != ' ' && copy[j] != '\t') {
+                               CG(zend_lineno) += newline_count;
+                               zend_throw_exception_ex(zend_ce_parse_error, 0, "Invalid body indentation level (expecting an indentation level of at least %d)", indentation);
+                               goto error;
+                       }
+
+                       if ((!using_spaces && copy[j] == ' ') || (using_spaces && copy[j] == '\t')) {
+                               CG(zend_lineno) += newline_count;
+                               zend_throw_exception(zend_ce_parse_error, "Invalid indentation - tabs and spaces cannot be mixed", 0);
+                               goto error;
+                       }
+               }
+
+               while (j < len && copy[j] != '\r' && copy[j] != '\n') {
+                       copy[i++] = copy[j++];
+               }
+
+               if (j == len) {
+                       break;
+               }
+skip:
+               if (copy[j] == '\r') {
+                       copy[i++] = copy[j++];
+                       trailing_newline = 1;
+               }
+
+               if (copy[j] == '\n') {
+                       copy[i++] = copy[j++];
+                       trailing_newline = 1;
+               }
+
+               if (trailing_newline) {
+                       ++newline_count;
+               }
+       }
+
+       if (YYSTATE != STATE(ST_END_HEREDOC) && trailing_newline && indentation) {
+               CG(zend_lineno) += newline_count;
+               zend_throw_exception_ex(zend_ce_parse_error, 0, "Invalid body indentation level (expecting an indentation level of at least %d)", indentation);
+               goto error;
+       }
+
+       Z_STRVAL_P(zendlval)[new_len - newline] = '\0';
+       Z_STRLEN_P(zendlval) = new_len - newline;
+
+       return 1;
+
+error:
+       zval_dtor(zendlval);
+       ZVAL_UNDEF(zendlval);
+
+       return 0;
+}
+
+static void copy_heredoc_label_stack(void *void_heredoc_label)
+{
+       zend_heredoc_label *heredoc_label = void_heredoc_label;
+       zend_heredoc_label *new_heredoc_label = emalloc(sizeof(zend_heredoc_label));
+
+       *new_heredoc_label = *heredoc_label;
+       new_heredoc_label->label = estrndup(heredoc_label->label, heredoc_label->length);
+
+       zend_ptr_stack_push(&SCNG(heredoc_label_stack), (void *) new_heredoc_label);
+}
+
 #define PARSER_MODE() \
        EXPECTED(elem != NULL)
 
@@ -2164,8 +2248,10 @@ skip_escape_conversion:
 
 <ST_IN_SCRIPTING>b?"<<<"{TABS_AND_SPACES}({LABEL}|([']{LABEL}['])|(["]{LABEL}["])){NEWLINE} {
        char *s;
-       int bprefix = (yytext[0] != '<') ? 1 : 0;
+       unsigned char *saved_cursor;
+       int bprefix = (yytext[0] != '<') ? 1 : 0, spacing = 0, indentation = 0;
        zend_heredoc_label *heredoc_label = emalloc(sizeof(zend_heredoc_label));
+       zend_bool is_heredoc = 1;
 
        CG(zend_lineno)++;
        heredoc_label->length = yyleng-bprefix-3-1-(yytext[yyleng-2]=='\r'?1:0);
@@ -2178,6 +2264,7 @@ skip_escape_conversion:
        if (*s == '\'') {
                s++;
                heredoc_label->length -= 2;
+               is_heredoc = 0;
 
                BEGIN(ST_NOWDOC);
        } else {
@@ -2190,21 +2277,101 @@ skip_escape_conversion:
        }
 
        heredoc_label->label = estrndup(s, heredoc_label->length);
+       heredoc_label->indentation = 0;
+       saved_cursor = YYCURSOR;
+
+       zend_ptr_stack_push(&SCNG(heredoc_label_stack), (void *) heredoc_label);
+
+       while (YYCURSOR < YYLIMIT && (*YYCURSOR == ' ' || *YYCURSOR == '\t')) {
+               if (*YYCURSOR == '\t') {
+                       spacing |= HEREDOC_USING_TABS;
+               } else {
+                       spacing |= HEREDOC_USING_SPACES;
+               }
+               ++YYCURSOR;
+               ++indentation;
+       }
+
+       if (YYCURSOR == YYLIMIT) {
+               YYCURSOR = saved_cursor;
+               RETURN_TOKEN(T_START_HEREDOC);
+       }
 
        /* Check for ending label on the next line */
        if (heredoc_label->length < YYLIMIT - YYCURSOR && !memcmp(YYCURSOR, s, heredoc_label->length)) {
-               YYCTYPE *end = YYCURSOR + heredoc_label->length;
+               if (!IS_LABEL_START(YYCURSOR[heredoc_label->length])) {
+                       if (spacing == (HEREDOC_USING_SPACES | HEREDOC_USING_TABS)) {
+                               zend_throw_exception(zend_ce_parse_error, "Invalid indentation - tabs and spaces cannot be mixed", 0);
+                       }
 
-               if (*end == ';') {
-                       end++;
-               }
+                       YYCURSOR = saved_cursor;
+                       heredoc_label->indentation = indentation;
 
-               if (*end == '\n' || *end == '\r') {
                        BEGIN(ST_END_HEREDOC);
+                       RETURN_TOKEN(T_START_HEREDOC);
                }
        }
 
-       zend_ptr_stack_push(&SCNG(heredoc_label_stack), (void *) heredoc_label);
+       YYCURSOR = saved_cursor;
+
+       if (is_heredoc && !SCNG(heredoc_scan_ahead)) {
+               zend_lex_state current_state;
+               int heredoc_nesting_level = 1;
+               int first_token = 0;
+
+               zend_save_lexical_state(&current_state);
+
+               SCNG(heredoc_scan_ahead) = 1;
+               SCNG(heredoc_indentation) = 0;
+               SCNG(heredoc_indentation_uses_spaces) = 0;
+               LANG_SCNG(on_event) = NULL;
+
+               zend_ptr_stack_reverse_apply(&current_state.heredoc_label_stack, copy_heredoc_label_stack);
+
+               while (heredoc_nesting_level) {
+                       zval zv;
+                       int retval;
+
+                       ZVAL_UNDEF(&zv);
+                       retval = lex_scan(&zv, NULL);
+                       zval_dtor(&zv);
+
+                       if (EG(exception)) {
+                               zend_clear_exception();
+                               break;
+                       }
+
+                       if (!first_token) {
+                               first_token = retval;
+                       }
+
+                       switch (retval) {
+                               case T_START_HEREDOC:
+                                       ++heredoc_nesting_level;
+                                       break;
+                               case T_END_HEREDOC:
+                                       --heredoc_nesting_level;
+                                       break;
+                               case END:
+                                       heredoc_nesting_level = 0;
+                       }
+               }
+
+               if (
+                   (first_token == T_VARIABLE
+                    || first_token == T_DOLLAR_OPEN_CURLY_BRACES
+                    || first_token == T_CURLY_OPEN
+                   ) && SCNG(heredoc_indentation)) {
+                       zend_throw_exception_ex(zend_ce_parse_error, 0, "Invalid body indentation level (expecting an indentation level of at least %d)", SCNG(heredoc_indentation));
+               }
+
+               heredoc_label->indentation = SCNG(heredoc_indentation);
+               heredoc_label->indentation_uses_spaces = SCNG(heredoc_indentation_uses_spaces);
+
+               zend_restore_lexical_state(&current_state);
+               SCNG(heredoc_scan_ahead) = 0;
+               CG(increment_lineno) = 0;
+       }
 
        RETURN_TOKEN(T_START_HEREDOC);
 }
@@ -2219,8 +2386,8 @@ skip_escape_conversion:
 <ST_END_HEREDOC>{ANY_CHAR} {
        zend_heredoc_label *heredoc_label = zend_ptr_stack_pop(&SCNG(heredoc_label_stack));
 
-       YYCURSOR += heredoc_label->length - 1;
-       yyleng = heredoc_label->length;
+       yyleng = heredoc_label->indentation + heredoc_label->length;
+       YYCURSOR += yyleng - 1;
 
        heredoc_label_dtor(heredoc_label);
        efree(heredoc_label);
@@ -2349,9 +2516,8 @@ double_quotes_scan_done:
 
 
 <ST_HEREDOC>{ANY_CHAR} {
-       int newline = 0;
-
        zend_heredoc_label *heredoc_label = zend_ptr_stack_top(&SCNG(heredoc_label_stack));
+       int newline = 0, indentation = 0, spacing = 0;
 
        if (YYCURSOR > YYLIMIT) {
                RETURN_TOKEN(END);
@@ -2367,28 +2533,55 @@ double_quotes_scan_done:
                                }
                                /* fall through */
                        case '\n':
+                               indentation = spacing = 0;
+
+                               while (YYCURSOR < YYLIMIT && (*YYCURSOR == ' ' || *YYCURSOR == '\t')) {
+                                       if (*YYCURSOR == '\t') {
+                                               spacing |= HEREDOC_USING_TABS;
+                                       } else {
+                                               spacing |= HEREDOC_USING_SPACES;
+                                       }
+                                       ++YYCURSOR;
+                                       ++indentation;
+                               }
+
+                               if (YYCURSOR == YYLIMIT) {
+                                       yyleng = YYCURSOR - SCNG(yy_text);
+                                       HANDLE_NEWLINES(yytext, yyleng);
+                                       ZVAL_NULL(zendlval);
+                                       RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
+                               }
+
                                /* Check for ending label on the next line */
                                if (IS_LABEL_START(*YYCURSOR) && heredoc_label->length < YYLIMIT - YYCURSOR && !memcmp(YYCURSOR, heredoc_label->label, heredoc_label->length)) {
-                                       YYCTYPE *end = YYCURSOR + heredoc_label->length;
+                                       if (IS_LABEL_START(YYCURSOR[heredoc_label->length])) {
+                                               continue;
+                                       }
 
-                                       if (*end == ';') {
-                                               end++;
+                                       if (spacing == (HEREDOC_USING_SPACES | HEREDOC_USING_TABS)) {
+                                               zend_throw_exception(zend_ce_parse_error, "Invalid indentation - tabs and spaces cannot be mixed", 0);
                                        }
 
-                                       if (*end == '\n' || *end == '\r') {
-                                               /* newline before label will be subtracted from returned text, but
-                                                * yyleng/yytext will include it, for zend_highlight/strip, tokenizer, etc. */
-                                               if (YYCURSOR[-2] == '\r' && YYCURSOR[-1] == '\n') {
-                                                       newline = 2; /* Windows newline */
-                                               } else {
-                                                       newline = 1;
-                                               }
+                                       /* newline before label will be subtracted from returned text, but
+                                        * yyleng/yytext will include it, for zend_highlight/strip, tokenizer, etc. */
+                                       if (YYCURSOR[-indentation - 2] == '\r' && YYCURSOR[-indentation - 1] == '\n') {
+                                               newline = 2; /* Windows newline */
+                                       } else {
+                                               newline = 1;
+                                       }
 
-                                               CG(increment_lineno) = 1; /* For newline before label */
-                                               BEGIN(ST_END_HEREDOC);
+                                       CG(increment_lineno) = 1; /* For newline before label */
 
-                                               goto heredoc_scan_done;
+                                       if (SCNG(heredoc_scan_ahead)) {
+                                               SCNG(heredoc_indentation) = indentation;
+                                               SCNG(heredoc_indentation_uses_spaces) = (spacing == HEREDOC_USING_SPACES);
+                                       } else {
+                                               YYCURSOR -= indentation;
                                        }
+
+                                       BEGIN(ST_END_HEREDOC);
+
+                                       goto heredoc_scan_done;
                                }
                                continue;
                        case '$':
@@ -2415,21 +2608,34 @@ double_quotes_scan_done:
        }
 
 heredoc_scan_done:
+
        yyleng = YYCURSOR - SCNG(yy_text);
+       ZVAL_STRINGL(zendlval, yytext, yyleng);
 
-       if (EXPECTED(zend_scan_escape_string(zendlval, yytext, yyleng - newline, 0) == SUCCESS)
-        || !PARSER_MODE()) {
-               RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
+       if (!SCNG(heredoc_scan_ahead) && !EG(exception) && PARSER_MODE()) {
+               zend_string *copy = Z_STR_P(zendlval);
+
+           if (!strip_multiline_string_indentation(zendlval, newline, heredoc_label->indentation, heredoc_label->indentation_uses_spaces)) {
+                       RETURN_TOKEN(T_ERROR);
+               }
+
+               if (UNEXPECTED(zend_scan_escape_string(zendlval, ZSTR_VAL(copy), ZSTR_LEN(copy), 0) != SUCCESS)) {
+                       zend_string_free(copy);
+                       RETURN_TOKEN(T_ERROR);
+               }
+
+               zend_string_free(copy);
        } else {
-               RETURN_TOKEN(T_ERROR);
+               HANDLE_NEWLINES(yytext, yyleng - newline);
        }
+
+       RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
 }
 
 
 <ST_NOWDOC>{ANY_CHAR} {
-       int newline = 0;
-
        zend_heredoc_label *heredoc_label = zend_ptr_stack_top(&SCNG(heredoc_label_stack));
+       int newline = 0, indentation = 0, spacing = -1;
 
        if (YYCURSOR > YYLIMIT) {
                RETURN_TOKEN(END);
@@ -2445,28 +2651,51 @@ heredoc_scan_done:
                                }
                                /* fall through */
                        case '\n':
+                               indentation = spacing = 0;
+
+                               while (YYCURSOR < YYLIMIT && (*YYCURSOR == ' ' || *YYCURSOR == '\t')) {
+                                       if (*YYCURSOR == '\t') {
+                                               spacing |= HEREDOC_USING_TABS;
+                                       } else {
+                                               spacing |= HEREDOC_USING_SPACES;
+                                       }
+                                       ++YYCURSOR;
+                                       ++indentation;
+                               }
+
+                               if (YYCURSOR == YYLIMIT) {
+                                       yyleng = YYCURSOR - SCNG(yy_text);
+                                       HANDLE_NEWLINES(yytext, yyleng);
+                                       ZVAL_NULL(zendlval);
+                                       RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
+                               }
+
                                /* Check for ending label on the next line */
                                if (IS_LABEL_START(*YYCURSOR) && heredoc_label->length < YYLIMIT - YYCURSOR && !memcmp(YYCURSOR, heredoc_label->label, heredoc_label->length)) {
-                                       YYCTYPE *end = YYCURSOR + heredoc_label->length;
+                                       if (IS_LABEL_START(YYCURSOR[heredoc_label->length])) {
+                                               continue;
+                                       }
 
-                                       if (*end == ';') {
-                                               end++;
+                                       if (spacing == (HEREDOC_USING_SPACES | HEREDOC_USING_TABS)) {
+                                               zend_throw_exception(zend_ce_parse_error, "Invalid indentation - tabs and spaces cannot be mixed", 0);
                                        }
 
-                                       if (*end == '\n' || *end == '\r') {
-                                               /* newline before label will be subtracted from returned text, but
-                                                * yyleng/yytext will include it, for zend_highlight/strip, tokenizer, etc. */
-                                               if (YYCURSOR[-2] == '\r' && YYCURSOR[-1] == '\n') {
-                                                       newline = 2; /* Windows newline */
-                                               } else {
-                                                       newline = 1;
-                                               }
+                                       /* newline before label will be subtracted from returned text, but
+                                        * yyleng/yytext will include it, for zend_highlight/strip, tokenizer, etc. */
+                                       if (YYCURSOR[-indentation - 2] == '\r' && YYCURSOR[-indentation - 1] == '\n') {
+                                               newline = 2; /* Windows newline */
+                                       } else {
+                                               newline = 1;
+                                       }
 
-                                               CG(increment_lineno) = 1; /* For newline before label */
-                                               BEGIN(ST_END_HEREDOC);
+                                       CG(increment_lineno) = 1; /* For newline before label */
 
-                                               goto nowdoc_scan_done;
-                                       }
+                                       YYCURSOR -= indentation;
+                                       heredoc_label->indentation = indentation;
+
+                                       BEGIN(ST_END_HEREDOC);
+
+                                       goto nowdoc_scan_done;
                                }
                                /* fall through */
                        default:
@@ -2476,8 +2705,13 @@ heredoc_scan_done:
 
 nowdoc_scan_done:
        yyleng = YYCURSOR - SCNG(yy_text);
+       ZVAL_STRINGL(zendlval, yytext, yyleng);
+
+       if (!EG(exception) && spacing != -1 && PARSER_MODE()
+           && !strip_multiline_string_indentation(zendlval, newline, indentation, spacing == HEREDOC_USING_SPACES)) {
+               RETURN_TOKEN(T_ERROR);
+       }
 
-       zend_copy_value(zendlval, yytext, yyleng - newline);
        HANDLE_NEWLINES(yytext, yyleng - newline);
        RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
 }
index 97643dc47daf0e7188959455ebb64d28139c7555..0c053c5ba4a466c2efdf7e7a5a89c440089ae260 100644 (file)
@@ -90,6 +90,15 @@ ZEND_API void zend_ptr_stack_apply(zend_ptr_stack *stack, void (*func)(void *))
        }
 }
 
+ZEND_API void zend_ptr_stack_reverse_apply(zend_ptr_stack *stack, void (*func)(void *))
+{
+       int i = 0;
+
+       while (i < stack->top) {
+               func(stack->elements[i++]);
+       }
+}
+
 
 ZEND_API void zend_ptr_stack_clean(zend_ptr_stack *stack, void (*func)(void *), zend_bool free_elements)
 {
index 80aa8bb252989a53f832e0ab5f3df5fc9bf49f13..01c1e0b87acde1bbe83abc3e924e9a2d457533df 100644 (file)
@@ -39,6 +39,7 @@ ZEND_API void zend_ptr_stack_n_push(zend_ptr_stack *stack, int count, ...);
 ZEND_API void zend_ptr_stack_n_pop(zend_ptr_stack *stack, int count, ...);
 ZEND_API void zend_ptr_stack_destroy(zend_ptr_stack *stack);
 ZEND_API void zend_ptr_stack_apply(zend_ptr_stack *stack, void (*func)(void *));
+ZEND_API void zend_ptr_stack_reverse_apply(zend_ptr_stack *stack, void (*func)(void *));
 ZEND_API void zend_ptr_stack_clean(zend_ptr_stack *stack, void (*func)(void *), zend_bool free_elements);
 ZEND_API int zend_ptr_stack_num_elements(zend_ptr_stack *stack);
 END_EXTERN_C()
diff --git a/ext/tokenizer/tests/token_get_all_heredoc_nowdoc.phpt b/ext/tokenizer/tests/token_get_all_heredoc_nowdoc.phpt
new file mode 100644 (file)
index 0000000..5f21c38
--- /dev/null
@@ -0,0 +1,416 @@
+--TEST--
+Flexible heredoc and nowdoc testing with token_get_all
+--SKIPIF--
+<?php if (!extension_loaded("tokenizer")) print "skip"; ?>
+--FILE--
+<?php
+
+function test(string $code, int $flags)
+{
+    try {
+        $tokens = token_get_all($code, $flags);
+        foreach ($tokens as $token) {
+            if (is_array($token)) {
+                echo "Line {$token[2]}: ", token_name($token[0]), " ('{$token[1]}')\n";
+            }
+        }
+    } catch (ParseError $e) {
+        echo "Parse error: {$e->getMessage()} on line {$e->getLine()}\n";
+    }
+}
+
+$tests = [];
+
+$tests[1] = <<<'OUTER_END'
+<?php <<<INNER_END
+INNER_END;
+OUTER_END;
+
+$tests[2] = <<<'OUTER_END'
+<?php <<<INNER_END
+  INNER_END;
+OUTER_END;
+
+$tests[3] = <<<'OUTER_END'
+<?php <<<'INNER_END'
+INNER_END;
+OUTER_END;
+
+$tests[4] = <<<'OUTER_END'
+<?php <<<'INNER_END'
+  INNER_END;
+OUTER_END;
+
+$tests[5] = <<<'OUTER_END'
+  <?php <<<INNER_END
+  a
+  OUTER_END;
+
+$tests[6] = <<<'OUTER_END'
+<?php <<<INNER_END
+ab
+OUTER_END;
+
+$tests[7] = <<<'OUTER_END'
+<?php <<<INNER_END
+a
+INNER_END;
+OUTER_END;
+
+$tests[8] = <<<CODE
+<?php
+ \t<<<'DOC'
+ \tXXX
+ \tDOC;
+CODE;
+
+$tests[9] = <<<'OUTER_END'
+<?php <<<INNER_END
+ab
+INNER_END;
+OUTER_END;
+
+$tests[10] = <<<'OUTER_END'
+<?php <<<INNER_END
+  ab
+  INNER_END;
+OUTER_END;
+
+$tests[11] = <<<'OUTER_END'
+<?php <<<INNER_END
+abc
+   INNER_END;
+OUTER_END;
+
+$tests[12] = <<<'OUTER_END'
+<?php <<<INNER_END
+
+   INNER_END;
+OUTER_END;
+
+$tests[13] = <<<'OUTER_END'
+<?php <<<'INNER_END'
+ab
+INNER_END;
+OUTER_END;
+
+$tests[14] = <<<'OUTER_END'
+<?php <<<'INNER_END'
+  ab
+  INNER_END;
+OUTER_END;
+
+$tests[15] = <<<'OUTER_END'
+<?php <<<'INNER_END'
+abc
+   INNER_END;
+OUTER_END;
+
+$tests[16] = <<<'OUTER_END'
+<?php <<<'INNER_END'
+
+   INNER_END;
+OUTER_END;
+
+$tests[17] = <<<OUTER_END
+<?php <<<INNER_END
+ab
+ab
+\t
+OUTER_END;
+
+$tests[18] = <<<OUTER_END
+<?php <<<'INNER_END'
+ab
+ab
+\t
+OUTER_END;
+
+echo "Without TOKEN_PARSE:\n";
+foreach ($tests as $i => $test) {
+    echo "\nTest case $i\n\n";
+    test($test, 0);
+}
+
+echo "\nWith TOKEN_PARSE:\n";
+foreach ($tests as $i => $test) {
+    echo "\nTest case $i\n\n";
+    test($test, TOKEN_PARSE);
+}
+
+?>
+--EXPECT--
+Without TOKEN_PARSE:
+
+Test case 1
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_END_HEREDOC ('INNER_END')
+
+Test case 2
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_END_HEREDOC ('  INNER_END')
+
+Test case 3
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_END_HEREDOC ('INNER_END')
+
+Test case 4
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_END_HEREDOC ('  INNER_END')
+
+Test case 5
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('a')
+
+Test case 6
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('ab')
+
+Test case 7
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('a
+')
+Line 3: T_END_HEREDOC ('INNER_END')
+
+Test case 8
+
+Line 1: T_OPEN_TAG ('<?php
+')
+Line 2: T_WHITESPACE ('        ')
+Line 2: T_START_HEREDOC ('<<<'DOC'
+')
+Line 3: T_ENCAPSED_AND_WHITESPACE ('   XXX
+')
+Line 4: T_END_HEREDOC ('       DOC')
+
+Test case 9
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('ab
+')
+Line 3: T_END_HEREDOC ('INNER_END')
+
+Test case 10
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('  ab
+')
+Line 3: T_END_HEREDOC ('  INNER_END')
+
+Test case 11
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('abc
+')
+Line 3: T_END_HEREDOC ('   INNER_END')
+
+Test case 12
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('
+')
+Line 3: T_END_HEREDOC ('   INNER_END')
+
+Test case 13
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('ab
+')
+Line 3: T_END_HEREDOC ('INNER_END')
+
+Test case 14
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('  ab
+')
+Line 3: T_END_HEREDOC ('  INNER_END')
+
+Test case 15
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('abc
+')
+Line 3: T_END_HEREDOC ('   INNER_END')
+
+Test case 16
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('
+')
+Line 3: T_END_HEREDOC ('   INNER_END')
+
+Test case 17
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('ab
+ab
+       ')
+
+Test case 18
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('ab
+ab
+       ')
+
+With TOKEN_PARSE:
+
+Test case 1
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_END_HEREDOC ('INNER_END')
+
+Test case 2
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_END_HEREDOC ('  INNER_END')
+
+Test case 3
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_END_HEREDOC ('INNER_END')
+
+Test case 4
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_END_HEREDOC ('  INNER_END')
+
+Test case 5
+
+Parse error: syntax error, unexpected end of file, expecting variable (T_VARIABLE) or heredoc end (T_END_HEREDOC) or ${ (T_DOLLAR_OPEN_CURLY_BRACES) or {$ (T_CURLY_OPEN) on line 2
+
+Test case 6
+
+Parse error: syntax error, unexpected end of file, expecting variable (T_VARIABLE) or heredoc end (T_END_HEREDOC) or ${ (T_DOLLAR_OPEN_CURLY_BRACES) or {$ (T_CURLY_OPEN) on line 2
+
+Test case 7
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('a
+')
+Line 3: T_END_HEREDOC ('INNER_END')
+
+Test case 8
+
+Parse error: Invalid indentation - tabs and spaces cannot be mixed on line 3
+
+Test case 9
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('ab
+')
+Line 3: T_END_HEREDOC ('INNER_END')
+
+Test case 10
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('  ab
+')
+Line 3: T_END_HEREDOC ('  INNER_END')
+
+Test case 11
+
+Parse error: Invalid body indentation level (expecting an indentation level of at least 3) on line 2
+
+Test case 12
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<INNER_END
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('
+')
+Line 3: T_END_HEREDOC ('   INNER_END')
+
+Test case 13
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('ab
+')
+Line 3: T_END_HEREDOC ('INNER_END')
+
+Test case 14
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('  ab
+')
+Line 3: T_END_HEREDOC ('  INNER_END')
+
+Test case 15
+
+Parse error: Invalid body indentation level (expecting an indentation level of at least 3) on line 2
+
+Test case 16
+
+Line 1: T_OPEN_TAG ('<?php ')
+Line 1: T_START_HEREDOC ('<<<'INNER_END'
+')
+Line 2: T_ENCAPSED_AND_WHITESPACE ('
+')
+Line 3: T_END_HEREDOC ('   INNER_END')
+
+Test case 17
+
+Parse error: syntax error, unexpected end of file, expecting variable (T_VARIABLE) or heredoc end (T_END_HEREDOC) or ${ (T_DOLLAR_OPEN_CURLY_BRACES) or {$ (T_CURLY_OPEN) on line 4
+
+Test case 18
+
+Parse error: syntax error, unexpected end of file, expecting variable (T_VARIABLE) or heredoc end (T_END_HEREDOC) or ${ (T_DOLLAR_OPEN_CURLY_BRACES) or {$ (T_CURLY_OPEN) on line 4