diff --git a/CHANGELOG.md b/CHANGELOG.md index bebe9535..8eb91d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +- more strict in a few cases where code that was incorrect code (according to the elm compiler) was successfully parsed: tuples with >3 parts, record access on most things +- better performance, parsing should now be a lot faster + ## [7.3.4] - 2024-07-26 Parsing is faster by ~90%. A big thank you to [@lue-bird](https://github.com/lue-bird) for finding and introducing a huge amount of performance improvements. diff --git a/benchmark/src/Main.elm b/benchmark/src/Main.elm index 74eb83f2..b11232e7 100644 --- a/benchmark/src/Main.elm +++ b/benchmark/src/Main.elm @@ -10905,4 +10905,4 @@ maybeToList maybe = Just content -> [ content ] -""" \ No newline at end of file +""" diff --git a/elm.json b/elm.json index 0b83ce37..2eaf1631 100644 --- a/elm.json +++ b/elm.json @@ -34,11 +34,10 @@ "elm/core": "1.0.0 <= v < 2.0.0", "elm/json": "1.0.0 <= v < 2.0.0", "elm/parser": "1.0.0 <= v < 2.0.0", - "miniBill/elm-unicode": "1.0.2 <= v < 2.0.0", "rtfeldman/elm-hex": "1.0.0 <= v < 2.0.0", "stil4m/structured-writer": "1.0.1 <= v < 2.0.0" }, "test-dependencies": { "elm-explorations/test": "2.0.0 <= v < 3.0.0" } -} +} \ No newline at end of file diff --git a/src/Char/Extra.elm b/src/Char/Extra.elm index a0d73046..0ab12342 100644 --- a/src/Char/Extra.elm +++ b/src/Char/Extra.elm @@ -1,15 +1,355 @@ -module Char.Extra exposing (isAlphaNumFast) +module Char.Extra exposing (isLatinAlphaNumOrUnderscoreFast, isUtf16Surrogate, unicodeIsAlphaNumOrUnderscoreFast, unicodeIsLowerFast, unicodeIsUpperFast) +{-| Edited from [minibill/elm-unicode](https://package.elm-lang.org/packages/miniBill/elm-unicode/latest/) -isAlphaNumFast : Char -> Bool -isAlphaNumFast char = - -- Char.isAlphaNum does not reuse the same Char.toCode and is therefore slightly slower +Copyright 2021 Leonardo Taglialegne + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-} + +-- + + +unicodeIsLowerFast : Char -> Bool +unicodeIsLowerFast c = + let + code : Int + code = + Char.toCode c + + cString : String + cString = + String.fromChar c + in + charCodeIsLower code + || (if String.toLower cString == cString ++ "" && String.toUpper cString /= cString ++ "" then + code <= 0x0344 || 0x0346 <= code && code <= 0x216F || 0x2180 <= code && code <= 0x24CF || 0x24EA <= code && code <= 0x000F0000 + + else if code < 0xA7F9 then + if code < 0x2109 then + if code < 0x024E then + 0x0137 <= code && code <= 0x0138 || 0x018C <= code && code <= 0x018D || 0x0199 <= code && code <= 0x019B || 0x01AA <= code && code <= 0x01AB || 0x01B9 <= code && code <= 0x01BA || 0x01BD <= code && code <= 0x01BF || code == 0x0221 || 0x0233 <= code && code <= 0x0239 + + else + 0x024F <= code && code <= 0x0293 || 0x0295 <= code && code <= 0x02AF || 0x03FB <= code && code <= 0x03FC || 0x0560 <= code && code <= 0x0588 || 0x1D00 <= code && code <= 0x1D2B || 0x1D6B <= code && code <= 0x1D77 || 0x1D79 <= code && code <= 0x1D9A || 0x1E95 <= code && code <= 0x1E9D || code == 0x1E9F + + else if code < 0x2C70 then + code == 0x210A || 0x210E <= code && code <= 0x210F || code == 0x2113 || code == 0x212F || code == 0x2134 || code == 0x2139 || 0x213C <= code && code <= 0x213D || 0x2146 <= code && code <= 0x2149 + + else + code == 0x2C71 || 0x2C73 <= code && code <= 0x2C74 || 0x2C76 <= code && code <= 0x2C7B || 0x2CE3 <= code && code <= 0x2CE4 || 0xA72F <= code && code <= 0xA731 || 0xA771 <= code && code <= 0xA778 || code == 0xA78E || 0xA793 <= code && code <= 0xA795 || code == 0xA7AF || modBy 2 code == 1 && 0xA7D3 <= code && code <= 0xA7D5 + + else if code < 0x0001D621 then + if code < 0x0001D4BA then + code == 0xA7FA || 0xAB30 <= code && code <= 0xAB5A || 0xAB60 <= code && code <= 0xAB68 || 0x0001D41A <= code && code <= 0x0001D433 || 0x0001D44E <= code && code <= 0x0001D454 || 0x0001D456 <= code && code <= 0x0001D467 || 0x0001D482 <= code && code <= 0x0001D49B || 0x0001D4B6 <= code && code <= 0x0001D4B9 + + else + code == 0x0001D4BB || 0x0001D4BD <= code && code <= 0x0001D4C3 || 0x0001D4C5 <= code && code <= 0x0001D4CF || 0x0001D4EA <= code && code <= 0x0001D503 || 0x0001D51E <= code && code <= 0x0001D537 || 0x0001D552 <= code && code <= 0x0001D56B || 0x0001D586 <= code && code <= 0x0001D59F || 0x0001D5BA <= code && code <= 0x0001D5D3 || 0x0001D5EE <= code && code <= 0x0001D607 + + else if code < 0x0001D74F then + 0x0001D622 <= code && code <= 0x0001D63B || 0x0001D656 <= code && code <= 0x0001D66F || 0x0001D68A <= code && code <= 0x0001D6A5 || 0x0001D6C2 <= code && code <= 0x0001D6DA || 0x0001D6DC <= code && code <= 0x0001D6E1 || 0x0001D6FC <= code && code <= 0x0001D714 || 0x0001D716 <= code && code <= 0x0001D71B || 0x0001D736 <= code && code <= 0x0001D74E + + else + 0x0001D750 <= code && code <= 0x0001D755 || 0x0001D770 <= code && code <= 0x0001D788 || 0x0001D78A <= code && code <= 0x0001D78F || 0x0001D7AA <= code && code <= 0x0001D7C2 || 0x0001D7C4 <= code && code <= 0x0001D7C9 || code == 0x0001D7CB || 0x0001DF00 <= code && code <= 0x0001DF09 || 0x0001DF0B <= code && code <= 0x0001DF1E || 0x0001DF25 <= code && code <= 0x0001DF2A + ) + + +unicodeIsUpperFast : Char -> Bool +unicodeIsUpperFast c = + let + code : Int + code = + Char.toCode c + in + charCodeIsUpper code + || (let + cString : String + cString = + String.fromChar c + in + if String.toUpper cString == cString ++ "" && String.toLower cString /= cString ++ "" then + code <= 0x215F || 0x2170 <= code && code <= 0x24B5 || 0x24D0 <= code && code <= 0x000F0000 + + else if code < 0x0001D4CF then + if code < 0x213D then + 0x03D2 <= code && code <= 0x03D4 || code == 0x2102 || code == 0x2107 || 0x210B <= code && code <= 0x210D || 0x2110 <= code && code <= 0x2112 || code == 0x2115 || 0x2119 <= code && code <= 0x211D || code == 0x2124 || code == 0x2128 || 0x212A <= code && code <= 0x212D || 0x2130 <= code && code <= 0x2133 + + else + 0x213E <= code && code <= 0x213F || code == 0x2145 || 0x0001D400 <= code && code <= 0x0001D419 || 0x0001D434 <= code && code <= 0x0001D44D || 0x0001D468 <= code && code <= 0x0001D481 || code == 0x0001D49C || 0x0001D49E <= code && code <= 0x0001D49F || code == 0x0001D4A2 || 0x0001D4A5 <= code && code <= 0x0001D4A6 || 0x0001D4A9 <= code && code <= 0x0001D4AC || 0x0001D4AE <= code && code <= 0x0001D4B5 + + else if code < 0x0001D59F then + 0x0001D4D0 <= code && code <= 0x0001D4E9 || 0x0001D504 <= code && code <= 0x0001D505 || 0x0001D507 <= code && code <= 0x0001D50A || 0x0001D50D <= code && code <= 0x0001D514 || 0x0001D516 <= code && code <= 0x0001D51C || 0x0001D538 <= code && code <= 0x0001D539 || 0x0001D53B <= code && code <= 0x0001D53E || 0x0001D540 <= code && code <= 0x0001D544 || code == 0x0001D546 || 0x0001D54A <= code && code <= 0x0001D550 || 0x0001D56C <= code && code <= 0x0001D585 + + else + 0x0001D5A0 <= code && code <= 0x0001D5B9 || 0x0001D5D4 <= code && code <= 0x0001D5ED || 0x0001D608 <= code && code <= 0x0001D621 || 0x0001D63C <= code && code <= 0x0001D655 || 0x0001D670 <= code && code <= 0x0001D689 || 0x0001D6A8 <= code && code <= 0x0001D6C0 || 0x0001D6E2 <= code && code <= 0x0001D6FA || 0x0001D71C <= code && code <= 0x0001D734 || 0x0001D756 <= code && code <= 0x0001D76E || 0x0001D790 <= code && code <= 0x0001D7A8 || code == 0x0001D7CA + ) + + +isLatinAlphaNumOrUnderscoreFast : Char -> Bool +isLatinAlphaNumOrUnderscoreFast c = let - charCode : Int - charCode = - char |> Char.toCode + code : Int + code = + Char.toCode c in - charCodeIsLower charCode || charCodeIsUpper charCode || charCodeIsDigit charCode + charCodeIsLower code + || charCodeIsUpper code + || charCodeIsDigit code + || -- (c == '_') + (code == 95) + + +unicodeIsAlphaNumOrUnderscoreFast : Char -> Bool +unicodeIsAlphaNumOrUnderscoreFast c = + let + code : Int + code = + Char.toCode c + in + charCodeIsLower code + || charCodeIsUpper code + || charCodeIsDigit code + || -- (c == '_') + (code == 95) + || -- if it's not obviously alphanum, + -- check if it's not some common end character and shortcut to False if possible + -- ((c /= ' ') && (c /= '\n')) + ((code /= 32) && (code /= 10)) + && (if code < 0x0100 then + 0x30 <= code && code <= 0x39 || 0x41 <= code && code <= 0x5A || 0x61 <= code && code <= 0x7A || code == 0xAA || 0xB2 <= code && code <= 0xB3 || code == 0xB5 || 0xB9 <= code && code <= 0xBA || 0xBC <= code && code <= 0xBE || 0xC0 <= code && code <= 0xD6 || 0xD8 <= code && code <= 0xF6 || 0xF8 <= code && code <= 0xFF + + else if code < 0xAAB4 then + if code < 0x10FB then + if code < 0x0B34 then + if code < 0x093C then + if code < 0x0670 then + if code < 0x03A2 then + 0x0100 <= code && code <= 0x02C1 || 0x02C6 <= code && code <= 0x02D1 || 0x02E0 <= code && code <= 0x02E4 || 0x0370 <= code && code <= 0x0374 || 0x0376 <= code && code <= 0x0377 || 0x037A <= code && code <= 0x037D || code == 0x037F || code == 0x0386 || 0x0388 <= code && code <= 0x038A || code == 0x038C || 0x038E <= code && code <= 0x03A1 || modBy 2 code == 0 && 0x02EC <= code && code <= 0x02EE + + else + 0x03A3 <= code && code <= 0x03F5 || 0x03F7 <= code && code <= 0x0481 || 0x048A <= code && code <= 0x052F || 0x0531 <= code && code <= 0x0556 || code == 0x0559 || 0x0560 <= code && code <= 0x0588 || 0x05D0 <= code && code <= 0x05EA || 0x05EF <= code && code <= 0x05F2 || 0x0620 <= code && code <= 0x064A || 0x0660 <= code && code <= 0x0669 || 0x066E <= code && code <= 0x066F + + else if code < 0x07F9 then + 0x0671 <= code && code <= 0x06D3 || code == 0x06D5 || 0x06E5 <= code && code <= 0x06E6 || 0x06EE <= code && code <= 0x06FC || code == 0x06FF || code == 0x0710 || 0x0712 <= code && code <= 0x072F || 0x074D <= code && code <= 0x07A5 || code == 0x07B1 || 0x07C0 <= code && code <= 0x07EA || 0x07F4 <= code && code <= 0x07F5 + + else + code == 0x07FA || 0x0800 <= code && code <= 0x0815 || code == 0x081A || code == 0x0824 || code == 0x0828 || 0x0840 <= code && code <= 0x0858 || 0x0860 <= code && code <= 0x086A || 0x0870 <= code && code <= 0x0887 || 0x0889 <= code && code <= 0x088E || 0x08A0 <= code && code <= 0x08C9 || 0x0904 <= code && code <= 0x0939 + + else if code < 0x0A31 then + if code < 0x09BC then + code == 0x093D || code == 0x0950 || 0x0958 <= code && code <= 0x0961 || 0x0966 <= code && code <= 0x096F || 0x0971 <= code && code <= 0x0980 || 0x0985 <= code && code <= 0x098C || 0x098F <= code && code <= 0x0990 || 0x0993 <= code && code <= 0x09A8 || 0x09AA <= code && code <= 0x09B0 || code == 0x09B2 || 0x09B6 <= code && code <= 0x09B9 + + else + code == 0x09BD || code == 0x09CE || 0x09DC <= code && code <= 0x09DD || 0x09DF <= code && code <= 0x09E1 || 0x09E6 <= code && code <= 0x09F1 || 0x09F4 <= code && code <= 0x09F9 || code == 0x09FC || 0x0A05 <= code && code <= 0x0A0A || 0x0A0F <= code && code <= 0x0A10 || 0x0A13 <= code && code <= 0x0A28 || 0x0A2A <= code && code <= 0x0A30 + + else if code < 0x0AB1 then + 0x0A32 <= code && code <= 0x0A33 || 0x0A35 <= code && code <= 0x0A36 || 0x0A38 <= code && code <= 0x0A39 || 0x0A59 <= code && code <= 0x0A5C || code == 0x0A5E || 0x0A66 <= code && code <= 0x0A6F || 0x0A72 <= code && code <= 0x0A74 || 0x0A85 <= code && code <= 0x0A8D || 0x0A8F <= code && code <= 0x0A91 || 0x0A93 <= code && code <= 0x0AA8 || 0x0AAA <= code && code <= 0x0AB0 + + else + 0x0AB2 <= code && code <= 0x0AB3 || 0x0AB5 <= code && code <= 0x0AB9 || code == 0x0ABD || code == 0x0AD0 || 0x0AE0 <= code && code <= 0x0AE1 || 0x0AE6 <= code && code <= 0x0AEF || code == 0x0AF9 || 0x0B05 <= code && code <= 0x0B0C || 0x0B0F <= code && code <= 0x0B10 || 0x0B13 <= code && code <= 0x0B28 || 0x0B2A <= code && code <= 0x0B30 || 0x0B32 <= code && code <= 0x0B33 + + else if code < 0x0D53 then + if code < 0x0C3C then + if code < 0x0B9B then + 0x0B35 <= code && code <= 0x0B39 || code == 0x0B3D || 0x0B5C <= code && code <= 0x0B5D || 0x0B5F <= code && code <= 0x0B61 || 0x0B66 <= code && code <= 0x0B6F || 0x0B71 <= code && code <= 0x0B77 || code == 0x0B83 || 0x0B85 <= code && code <= 0x0B8A || 0x0B8E <= code && code <= 0x0B90 || 0x0B92 <= code && code <= 0x0B95 || 0x0B99 <= code && code <= 0x0B9A + + else + code == 0x0B9C || 0x0B9E <= code && code <= 0x0B9F || 0x0BA3 <= code && code <= 0x0BA4 || 0x0BA8 <= code && code <= 0x0BAA || 0x0BAE <= code && code <= 0x0BB9 || code == 0x0BD0 || 0x0BE6 <= code && code <= 0x0BF2 || 0x0C05 <= code && code <= 0x0C0C || 0x0C0E <= code && code <= 0x0C10 || 0x0C12 <= code && code <= 0x0C28 || 0x0C2A <= code && code <= 0x0C39 + + else if code < 0x0CB4 then + code == 0x0C3D || 0x0C58 <= code && code <= 0x0C5A || code == 0x0C5D || 0x0C60 <= code && code <= 0x0C61 || 0x0C66 <= code && code <= 0x0C6F || 0x0C78 <= code && code <= 0x0C7E || code == 0x0C80 || 0x0C85 <= code && code <= 0x0C8C || 0x0C8E <= code && code <= 0x0C90 || 0x0C92 <= code && code <= 0x0CA8 || 0x0CAA <= code && code <= 0x0CB3 + + else + 0x0CB5 <= code && code <= 0x0CB9 || code == 0x0CBD || 0x0CDD <= code && code <= 0x0CDE || 0x0CE0 <= code && code <= 0x0CE1 || 0x0CE6 <= code && code <= 0x0CEF || 0x0CF1 <= code && code <= 0x0CF2 || 0x0D04 <= code && code <= 0x0D0C || 0x0D0E <= code && code <= 0x0D10 || 0x0D12 <= code && code <= 0x0D3A || code == 0x0D3D || code == 0x0D4E + + else if code < 0x0EBF then + if code < 0x0E31 then + 0x0D54 <= code && code <= 0x0D56 || 0x0D58 <= code && code <= 0x0D61 || 0x0D66 <= code && code <= 0x0D78 || 0x0D7A <= code && code <= 0x0D7F || 0x0D85 <= code && code <= 0x0D96 || 0x0D9A <= code && code <= 0x0DB1 || 0x0DB3 <= code && code <= 0x0DBB || code == 0x0DBD || 0x0DC0 <= code && code <= 0x0DC6 || 0x0DE6 <= code && code <= 0x0DEF || 0x0E01 <= code && code <= 0x0E30 + + else + 0x0E32 <= code && code <= 0x0E33 || 0x0E40 <= code && code <= 0x0E46 || 0x0E50 <= code && code <= 0x0E59 || 0x0E81 <= code && code <= 0x0E82 || code == 0x0E84 || 0x0E86 <= code && code <= 0x0E8A || 0x0E8C <= code && code <= 0x0EA3 || code == 0x0EA5 || 0x0EA7 <= code && code <= 0x0EB0 || 0x0EB2 <= code && code <= 0x0EB3 || code == 0x0EBD + + else if code < 0x104F then + 0x0EC0 <= code && code <= 0x0EC4 || code == 0x0EC6 || 0x0ED0 <= code && code <= 0x0ED9 || 0x0EDC <= code && code <= 0x0EDF || code == 0x0F00 || 0x0F20 <= code && code <= 0x0F33 || 0x0F40 <= code && code <= 0x0F47 || 0x0F49 <= code && code <= 0x0F6C || 0x0F88 <= code && code <= 0x0F8C || 0x1000 <= code && code <= 0x102A || 0x103F <= code && code <= 0x1049 + + else + 0x1050 <= code && code <= 0x1055 || 0x105A <= code && code <= 0x105D || code == 0x1061 || 0x1065 <= code && code <= 0x1066 || 0x106E <= code && code <= 0x1070 || 0x1075 <= code && code <= 0x1081 || code == 0x108E || 0x1090 <= code && code <= 0x1099 || 0x10A0 <= code && code <= 0x10C5 || code == 0x10C7 || code == 0x10CD || 0x10D0 <= code && code <= 0x10FA + + else if code < 0x2106 then + if code < 0x197F then + if code < 0x1680 then + if code < 0x12C1 then + 0x10FC <= code && code <= 0x1248 || 0x124A <= code && code <= 0x124D || 0x1250 <= code && code <= 0x1256 || code == 0x1258 || 0x125A <= code && code <= 0x125D || 0x1260 <= code && code <= 0x1288 || 0x128A <= code && code <= 0x128D || 0x1290 <= code && code <= 0x12B0 || 0x12B2 <= code && code <= 0x12B5 || 0x12B8 <= code && code <= 0x12BE || code == 0x12C0 + + else + 0x12C2 <= code && code <= 0x12C5 || 0x12C8 <= code && code <= 0x12D6 || 0x12D8 <= code && code <= 0x1310 || 0x1312 <= code && code <= 0x1315 || 0x1318 <= code && code <= 0x135A || 0x1369 <= code && code <= 0x137C || 0x1380 <= code && code <= 0x138F || 0x13A0 <= code && code <= 0x13F5 || 0x13F8 <= code && code <= 0x13FD || 0x1401 <= code && code <= 0x166C || 0x166F <= code && code <= 0x167F + + else if code < 0x17DF then + 0x1681 <= code && code <= 0x169A || 0x16A0 <= code && code <= 0x16EA || 0x16EE <= code && code <= 0x16F8 || 0x1700 <= code && code <= 0x1711 || 0x171F <= code && code <= 0x1731 || 0x1740 <= code && code <= 0x1751 || 0x1760 <= code && code <= 0x176C || 0x176E <= code && code <= 0x1770 || 0x1780 <= code && code <= 0x17B3 || code == 0x17D7 || code == 0x17DC + + else + 0x17E0 <= code && code <= 0x17E9 || 0x17F0 <= code && code <= 0x17F9 || 0x1810 <= code && code <= 0x1819 || 0x1820 <= code && code <= 0x1878 || 0x1880 <= code && code <= 0x1884 || 0x1887 <= code && code <= 0x18A8 || code == 0x18AA || 0x18B0 <= code && code <= 0x18F5 || 0x1900 <= code && code <= 0x191E || 0x1946 <= code && code <= 0x196D || 0x1970 <= code && code <= 0x1974 + + else if code < 0x1CF9 then + if code < 0x1B82 then + 0x1980 <= code && code <= 0x19AB || 0x19B0 <= code && code <= 0x19C9 || 0x19D0 <= code && code <= 0x19DA || 0x1A00 <= code && code <= 0x1A16 || 0x1A20 <= code && code <= 0x1A54 || 0x1A80 <= code && code <= 0x1A89 || 0x1A90 <= code && code <= 0x1A99 || code == 0x1AA7 || 0x1B05 <= code && code <= 0x1B33 || 0x1B45 <= code && code <= 0x1B4C || 0x1B50 <= code && code <= 0x1B59 + + else + 0x1B83 <= code && code <= 0x1BA0 || 0x1BAE <= code && code <= 0x1BE5 || 0x1C00 <= code && code <= 0x1C23 || 0x1C40 <= code && code <= 0x1C49 || 0x1C4D <= code && code <= 0x1C7D || 0x1C80 <= code && code <= 0x1C88 || 0x1C90 <= code && code <= 0x1CBA || 0x1CBD <= code && code <= 0x1CBF || 0x1CE9 <= code && code <= 0x1CEC || 0x1CEE <= code && code <= 0x1CF3 || 0x1CF5 <= code && code <= 0x1CF6 + + else if code < 0x1FC1 then + code == 0x1CFA || 0x1D00 <= code && code <= 0x1DBF || 0x1E00 <= code && code <= 0x1F15 || 0x1F18 <= code && code <= 0x1F1D || 0x1F20 <= code && code <= 0x1F45 || 0x1F48 <= code && code <= 0x1F4D || 0x1F50 <= code && code <= 0x1F57 || 0x1F60 <= code && code <= 0x1F7D || 0x1F80 <= code && code <= 0x1FB4 || 0x1FB6 <= code && code <= 0x1FBC || code == 0x1FBE || modBy 2 code == 1 && 0x1F59 <= code && code <= 0x1F5F + + else + 0x1FC2 <= code && code <= 0x1FC4 || 0x1FC6 <= code && code <= 0x1FCC || 0x1FD0 <= code && code <= 0x1FD3 || 0x1FD6 <= code && code <= 0x1FDB || 0x1FE0 <= code && code <= 0x1FEC || 0x1FF2 <= code && code <= 0x1FF4 || 0x1FF6 <= code && code <= 0x1FFC || 0x2070 <= code && code <= 0x2071 || 0x2074 <= code && code <= 0x2079 || 0x207F <= code && code <= 0x2089 || 0x2090 <= code && code <= 0x209C || code == 0x2102 + + else if code < 0x31EF then + if code < 0x2D7F then + if code < 0x24E9 then + code == 0x2107 || 0x210A <= code && code <= 0x2113 || code == 0x2115 || 0x2119 <= code && code <= 0x211D || 0x212A <= code && code <= 0x212D || 0x212F <= code && code <= 0x2139 || 0x213C <= code && code <= 0x213F || 0x2145 <= code && code <= 0x2149 || code == 0x214E || 0x2150 <= code && code <= 0x2189 || 0x2460 <= code && code <= 0x249B || modBy 2 code == 0 && 0x2124 <= code && code <= 0x2128 + + else + 0x24EA <= code && code <= 0x24FF || 0x2776 <= code && code <= 0x2793 || 0x2C00 <= code && code <= 0x2CE4 || 0x2CEB <= code && code <= 0x2CEE || 0x2CF2 <= code && code <= 0x2CF3 || code == 0x2CFD || 0x2D00 <= code && code <= 0x2D25 || code == 0x2D27 || code == 0x2D2D || 0x2D30 <= code && code <= 0x2D67 || code == 0x2D6F + + else if code < 0x3020 then + 0x2D80 <= code && code <= 0x2D96 || 0x2DA0 <= code && code <= 0x2DA6 || 0x2DA8 <= code && code <= 0x2DAE || 0x2DB0 <= code && code <= 0x2DB6 || 0x2DB8 <= code && code <= 0x2DBE || 0x2DC0 <= code && code <= 0x2DC6 || 0x2DC8 <= code && code <= 0x2DCE || 0x2DD0 <= code && code <= 0x2DD6 || 0x2DD8 <= code && code <= 0x2DDE || code == 0x2E2F || 0x3005 <= code && code <= 0x3007 + + else + 0x3021 <= code && code <= 0x3029 || 0x3031 <= code && code <= 0x3035 || 0x3038 <= code && code <= 0x303C || 0x3041 <= code && code <= 0x3096 || 0x309D <= code && code <= 0x309F || 0x30A1 <= code && code <= 0x30FA || 0x30FC <= code && code <= 0x30FF || 0x3105 <= code && code <= 0x312F || 0x3131 <= code && code <= 0x318E || 0x3192 <= code && code <= 0x3195 || 0x31A0 <= code && code <= 0x31BF + + else if code < 0xA80B then + if code < 0xA63F then + 0x31F0 <= code && code <= 0x31FF || 0x3220 <= code && code <= 0x3229 || 0x3248 <= code && code <= 0x324F || 0x3251 <= code && code <= 0x325F || 0x3280 <= code && code <= 0x3289 || 0x32B1 <= code && code <= 0x32BF || 0x3400 <= code && code <= 0x4DBF || 0x4E00 <= code && code <= 0xA48C || 0xA4D0 <= code && code <= 0xA4FD || 0xA500 <= code && code <= 0xA60C || 0xA610 <= code && code <= 0xA62B + + else + 0xA640 <= code && code <= 0xA66E || 0xA67F <= code && code <= 0xA69D || 0xA6A0 <= code && code <= 0xA6EF || 0xA717 <= code && code <= 0xA71F || 0xA722 <= code && code <= 0xA788 || 0xA78B <= code && code <= 0xA7CA || 0xA7D0 <= code && code <= 0xA7D1 || 0xA7D6 <= code && code <= 0xA7D9 || 0xA7F2 <= code && code <= 0xA801 || 0xA803 <= code && code <= 0xA805 || 0xA807 <= code && code <= 0xA80A || modBy 2 code == 1 && 0xA7D3 <= code && code <= 0xA7D5 + + else if code < 0xA983 then + 0xA80C <= code && code <= 0xA822 || 0xA830 <= code && code <= 0xA835 || 0xA840 <= code && code <= 0xA873 || 0xA882 <= code && code <= 0xA8B3 || 0xA8D0 <= code && code <= 0xA8D9 || 0xA8F2 <= code && code <= 0xA8F7 || code == 0xA8FB || 0xA8FD <= code && code <= 0xA8FE || 0xA900 <= code && code <= 0xA925 || 0xA930 <= code && code <= 0xA946 || 0xA960 <= code && code <= 0xA97C + + else + 0xA984 <= code && code <= 0xA9B2 || 0xA9CF <= code && code <= 0xA9D9 || 0xA9E0 <= code && code <= 0xA9E4 || 0xA9E6 <= code && code <= 0xA9FE || 0xAA00 <= code && code <= 0xAA28 || 0xAA40 <= code && code <= 0xAA42 || 0xAA44 <= code && code <= 0xAA4B || 0xAA50 <= code && code <= 0xAA59 || 0xAA60 <= code && code <= 0xAA76 || code == 0xAA7A || 0xAA7E <= code && code <= 0xAAAF || code == 0xAAB1 + + else if code < 0x000116B7 then + if code < 0x00010857 then + if code < 0x0001000C then + if code < 0xFB1E then + if code < 0xAB5B then + 0xAAB5 <= code && code <= 0xAAB6 || 0xAAB9 <= code && code <= 0xAABD || 0xAADB <= code && code <= 0xAADD || 0xAAE0 <= code && code <= 0xAAEA || 0xAAF2 <= code && code <= 0xAAF4 || 0xAB01 <= code && code <= 0xAB06 || 0xAB09 <= code && code <= 0xAB0E || 0xAB11 <= code && code <= 0xAB16 || 0xAB20 <= code && code <= 0xAB26 || 0xAB28 <= code && code <= 0xAB2E || 0xAB30 <= code && code <= 0xAB5A || modBy 2 code == 0 && 0xAAC0 <= code && code <= 0xAAC2 + + else + 0xAB5C <= code && code <= 0xAB69 || 0xAB70 <= code && code <= 0xABE2 || 0xABF0 <= code && code <= 0xABF9 || 0xAC00 <= code && code <= 0xD7A3 || 0xD7B0 <= code && code <= 0xD7C6 || 0xD7CB <= code && code <= 0xD7FB || 0xF900 <= code && code <= 0xFA6D || 0xFA70 <= code && code <= 0xFAD9 || 0xFB00 <= code && code <= 0xFB06 || 0xFB13 <= code && code <= 0xFB17 || code == 0xFB1D + + else if code < 0xFE6F then + 0xFB1F <= code && code <= 0xFB28 || 0xFB2A <= code && code <= 0xFB36 || 0xFB38 <= code && code <= 0xFB3C || code == 0xFB3E || 0xFB40 <= code && code <= 0xFB41 || 0xFB43 <= code && code <= 0xFB44 || 0xFB46 <= code && code <= 0xFBB1 || 0xFBD3 <= code && code <= 0xFD3D || 0xFD50 <= code && code <= 0xFD8F || 0xFD92 <= code && code <= 0xFDC7 || 0xFDF0 <= code && code <= 0xFDFB + + else + 0xFE70 <= code && code <= 0xFE74 || 0xFE76 <= code && code <= 0xFEFC || 0xFF10 <= code && code <= 0xFF19 || 0xFF21 <= code && code <= 0xFF3A || 0xFF41 <= code && code <= 0xFF5A || 0xFF66 <= code && code <= 0xFFBE || 0xFFC2 <= code && code <= 0xFFC7 || 0xFFCA <= code && code <= 0xFFCF || 0xFFD2 <= code && code <= 0xFFD7 || 0xFFDA <= code && code <= 0xFFDC || 0x00010000 <= code && code <= 0x0001000B + + else if code < 0x000104D7 then + if code < 0x000102E0 then + 0x0001000D <= code && code <= 0x00010026 || 0x00010028 <= code && code <= 0x0001003A || 0x0001003C <= code && code <= 0x0001003D || 0x0001003F <= code && code <= 0x0001004D || 0x00010050 <= code && code <= 0x0001005D || 0x00010080 <= code && code <= 0x000100FA || 0x00010107 <= code && code <= 0x00010133 || 0x00010140 <= code && code <= 0x00010178 || 0x0001018A <= code && code <= 0x0001018B || 0x00010280 <= code && code <= 0x0001029C || 0x000102A0 <= code && code <= 0x000102D0 + + else + 0x000102E1 <= code && code <= 0x000102FB || 0x00010300 <= code && code <= 0x00010323 || 0x0001032D <= code && code <= 0x0001034A || 0x00010350 <= code && code <= 0x00010375 || 0x00010380 <= code && code <= 0x0001039D || 0x000103A0 <= code && code <= 0x000103C3 || 0x000103C8 <= code && code <= 0x000103CF || 0x000103D1 <= code && code <= 0x000103D5 || 0x00010400 <= code && code <= 0x0001049D || 0x000104A0 <= code && code <= 0x000104A9 || 0x000104B0 <= code && code <= 0x000104D3 + + else if code < 0x000105FF then + 0x000104D8 <= code && code <= 0x000104FB || 0x00010500 <= code && code <= 0x00010527 || 0x00010530 <= code && code <= 0x00010563 || 0x00010570 <= code && code <= 0x0001057A || 0x0001057C <= code && code <= 0x0001058A || 0x0001058C <= code && code <= 0x00010592 || 0x00010594 <= code && code <= 0x00010595 || 0x00010597 <= code && code <= 0x000105A1 || 0x000105A3 <= code && code <= 0x000105B1 || 0x000105B3 <= code && code <= 0x000105B9 || 0x000105BB <= code && code <= 0x000105BC + + else + 0x00010600 <= code && code <= 0x00010736 || 0x00010740 <= code && code <= 0x00010755 || 0x00010760 <= code && code <= 0x00010767 || 0x00010780 <= code && code <= 0x00010785 || 0x00010787 <= code && code <= 0x000107B0 || 0x000107B2 <= code && code <= 0x000107BA || 0x00010800 <= code && code <= 0x00010805 || code == 0x00010808 || 0x0001080A <= code && code <= 0x00010835 || 0x00010837 <= code && code <= 0x00010838 || code == 0x0001083C || 0x0001083F <= code && code <= 0x00010855 + + else if code < 0x000110EF then + if code < 0x00010B77 then + if code < 0x00010A14 then + 0x00010858 <= code && code <= 0x00010876 || 0x00010879 <= code && code <= 0x0001089E || 0x000108A7 <= code && code <= 0x000108AF || 0x000108E0 <= code && code <= 0x000108F2 || 0x000108F4 <= code && code <= 0x000108F5 || 0x000108FB <= code && code <= 0x0001091B || 0x00010920 <= code && code <= 0x00010939 || 0x00010980 <= code && code <= 0x000109B7 || 0x000109BC <= code && code <= 0x000109CF || 0x000109D2 <= code && code <= 0x00010A00 || 0x00010A10 <= code && code <= 0x00010A13 + + else + 0x00010A15 <= code && code <= 0x00010A17 || 0x00010A19 <= code && code <= 0x00010A35 || 0x00010A40 <= code && code <= 0x00010A48 || 0x00010A60 <= code && code <= 0x00010A7E || 0x00010A80 <= code && code <= 0x00010A9F || 0x00010AC0 <= code && code <= 0x00010AC7 || 0x00010AC9 <= code && code <= 0x00010AE4 || 0x00010AEB <= code && code <= 0x00010AEF || 0x00010B00 <= code && code <= 0x00010B35 || 0x00010B40 <= code && code <= 0x00010B55 || 0x00010B58 <= code && code <= 0x00010B72 + + else if code < 0x00010F2F then + 0x00010B78 <= code && code <= 0x00010B91 || 0x00010BA9 <= code && code <= 0x00010BAF || 0x00010C00 <= code && code <= 0x00010C48 || 0x00010C80 <= code && code <= 0x00010CB2 || 0x00010CC0 <= code && code <= 0x00010CF2 || 0x00010CFA <= code && code <= 0x00010D23 || 0x00010D30 <= code && code <= 0x00010D39 || 0x00010E60 <= code && code <= 0x00010E7E || 0x00010E80 <= code && code <= 0x00010EA9 || 0x00010EB0 <= code && code <= 0x00010EB1 || 0x00010F00 <= code && code <= 0x00010F27 + + else + 0x00010F30 <= code && code <= 0x00010F45 || 0x00010F51 <= code && code <= 0x00010F54 || 0x00010F70 <= code && code <= 0x00010F81 || 0x00010FB0 <= code && code <= 0x00010FCB || 0x00010FE0 <= code && code <= 0x00010FF6 || 0x00011003 <= code && code <= 0x00011037 || 0x00011052 <= code && code <= 0x0001106F || 0x00011071 <= code && code <= 0x00011072 || code == 0x00011075 || 0x00011083 <= code && code <= 0x000110AF || 0x000110D0 <= code && code <= 0x000110E8 + + else if code < 0x00011304 then + if code < 0x000111E0 then + 0x000110F0 <= code && code <= 0x000110F9 || 0x00011103 <= code && code <= 0x00011126 || 0x00011136 <= code && code <= 0x0001113F || code == 0x00011144 || code == 0x00011147 || 0x00011150 <= code && code <= 0x00011172 || code == 0x00011176 || 0x00011183 <= code && code <= 0x000111B2 || 0x000111C1 <= code && code <= 0x000111C4 || 0x000111D0 <= code && code <= 0x000111DA || code == 0x000111DC + + else + 0x000111E1 <= code && code <= 0x000111F4 || 0x00011200 <= code && code <= 0x00011211 || 0x00011213 <= code && code <= 0x0001122B || 0x0001123F <= code && code <= 0x00011240 || 0x00011280 <= code && code <= 0x00011286 || code == 0x00011288 || 0x0001128A <= code && code <= 0x0001128D || 0x0001128F <= code && code <= 0x0001129D || 0x0001129F <= code && code <= 0x000112A8 || 0x000112B0 <= code && code <= 0x000112DE || 0x000112F0 <= code && code <= 0x000112F9 + + else if code < 0x0001144F then + 0x00011305 <= code && code <= 0x0001130C || 0x0001130F <= code && code <= 0x00011310 || 0x00011313 <= code && code <= 0x00011328 || 0x0001132A <= code && code <= 0x00011330 || 0x00011332 <= code && code <= 0x00011333 || 0x00011335 <= code && code <= 0x00011339 || code == 0x0001133D || code == 0x00011350 || 0x0001135D <= code && code <= 0x00011361 || 0x00011400 <= code && code <= 0x00011434 || 0x00011447 <= code && code <= 0x0001144A + + else + 0x00011450 <= code && code <= 0x00011459 || 0x0001145F <= code && code <= 0x00011461 || 0x00011480 <= code && code <= 0x000114AF || 0x000114C4 <= code && code <= 0x000114C5 || code == 0x000114C7 || 0x000114D0 <= code && code <= 0x000114D9 || 0x00011580 <= code && code <= 0x000115AE || 0x000115D8 <= code && code <= 0x000115DB || 0x00011600 <= code && code <= 0x0001162F || code == 0x00011644 || 0x00011650 <= code && code <= 0x00011659 || 0x00011680 <= code && code <= 0x000116AA + + else if code < 0x0001D455 then + if code < 0x00011FFF then + if code < 0x00011BFF then + if code < 0x00011917 then + code == 0x000116B8 || 0x000116C0 <= code && code <= 0x000116C9 || 0x00011700 <= code && code <= 0x0001171A || 0x00011730 <= code && code <= 0x0001173B || 0x00011740 <= code && code <= 0x00011746 || 0x00011800 <= code && code <= 0x0001182B || 0x000118A0 <= code && code <= 0x000118F2 || 0x000118FF <= code && code <= 0x00011906 || code == 0x00011909 || 0x0001190C <= code && code <= 0x00011913 || 0x00011915 <= code && code <= 0x00011916 + + else + 0x00011918 <= code && code <= 0x0001192F || 0x00011950 <= code && code <= 0x00011959 || 0x000119A0 <= code && code <= 0x000119A7 || 0x000119AA <= code && code <= 0x000119D0 || code == 0x00011A00 || 0x00011A0B <= code && code <= 0x00011A32 || code == 0x00011A3A || code == 0x00011A50 || 0x00011A5C <= code && code <= 0x00011A89 || code == 0x00011A9D || 0x00011AB0 <= code && code <= 0x00011AF8 || modBy 2 code == 1 && (0x0001193F <= code && code <= 0x00011941 || 0x000119E1 <= code && code <= 0x000119E3) + + else if code < 0x00011D66 then + 0x00011C00 <= code && code <= 0x00011C08 || 0x00011C0A <= code && code <= 0x00011C2E || code == 0x00011C40 || 0x00011C50 <= code && code <= 0x00011C6C || 0x00011C72 <= code && code <= 0x00011C8F || 0x00011D00 <= code && code <= 0x00011D06 || 0x00011D08 <= code && code <= 0x00011D09 || 0x00011D0B <= code && code <= 0x00011D30 || code == 0x00011D46 || 0x00011D50 <= code && code <= 0x00011D59 || 0x00011D60 <= code && code <= 0x00011D65 + + else + 0x00011D67 <= code && code <= 0x00011D68 || 0x00011D6A <= code && code <= 0x00011D89 || code == 0x00011D98 || 0x00011DA0 <= code && code <= 0x00011DA9 || 0x00011EE0 <= code && code <= 0x00011EF2 || code == 0x00011F02 || 0x00011F04 <= code && code <= 0x00011F10 || 0x00011F12 <= code && code <= 0x00011F33 || 0x00011F50 <= code && code <= 0x00011F59 || code == 0x00011FB0 || 0x00011FC0 <= code && code <= 0x00011FD4 + + else if code < 0x00016F92 then + if code < 0x00016ABF then + 0x00012000 <= code && code <= 0x00012399 || 0x00012400 <= code && code <= 0x0001246E || 0x00012480 <= code && code <= 0x00012543 || 0x00012F90 <= code && code <= 0x00012FF0 || 0x00013000 <= code && code <= 0x0001342F || 0x00013441 <= code && code <= 0x00013446 || 0x00014400 <= code && code <= 0x00014646 || 0x00016800 <= code && code <= 0x00016A38 || 0x00016A40 <= code && code <= 0x00016A5E || 0x00016A60 <= code && code <= 0x00016A69 || 0x00016A70 <= code && code <= 0x00016ABE + + else + 0x00016AC0 <= code && code <= 0x00016AC9 || 0x00016AD0 <= code && code <= 0x00016AED || 0x00016B00 <= code && code <= 0x00016B2F || 0x00016B40 <= code && code <= 0x00016B43 || 0x00016B50 <= code && code <= 0x00016B59 || 0x00016B5B <= code && code <= 0x00016B61 || 0x00016B63 <= code && code <= 0x00016B77 || 0x00016B7D <= code && code <= 0x00016B8F || 0x00016E40 <= code && code <= 0x00016E96 || 0x00016F00 <= code && code <= 0x00016F4A || code == 0x00016F50 + + else if code < 0x0001B14F then + 0x00016F93 <= code && code <= 0x00016F9F || 0x00016FE0 <= code && code <= 0x00016FE1 || code == 0x00016FE3 || 0x00017000 <= code && code <= 0x000187F7 || 0x00018800 <= code && code <= 0x00018CD5 || 0x00018D00 <= code && code <= 0x00018D08 || 0x0001AFF0 <= code && code <= 0x0001AFF3 || 0x0001AFF5 <= code && code <= 0x0001AFFB || 0x0001AFFD <= code && code <= 0x0001AFFE || 0x0001B000 <= code && code <= 0x0001B122 || code == 0x0001B132 + + else + 0x0001B150 <= code && code <= 0x0001B152 || code == 0x0001B155 || 0x0001B164 <= code && code <= 0x0001B167 || 0x0001B170 <= code && code <= 0x0001B2FB || 0x0001BC00 <= code && code <= 0x0001BC6A || 0x0001BC70 <= code && code <= 0x0001BC7C || 0x0001BC80 <= code && code <= 0x0001BC88 || 0x0001BC90 <= code && code <= 0x0001BC99 || 0x0001D2C0 <= code && code <= 0x0001D2D3 || 0x0001D2E0 <= code && code <= 0x0001D2F3 || 0x0001D360 <= code && code <= 0x0001D378 || 0x0001D400 <= code && code <= 0x0001D454 + + else if code < 0x0001E7EF then + if code < 0x0001D715 then + if code < 0x0001D515 then + 0x0001D456 <= code && code <= 0x0001D49C || 0x0001D49E <= code && code <= 0x0001D49F || code == 0x0001D4A2 || 0x0001D4A5 <= code && code <= 0x0001D4A6 || 0x0001D4A9 <= code && code <= 0x0001D4AC || 0x0001D4AE <= code && code <= 0x0001D4B9 || code == 0x0001D4BB || 0x0001D4BD <= code && code <= 0x0001D4C3 || 0x0001D4C5 <= code && code <= 0x0001D505 || 0x0001D507 <= code && code <= 0x0001D50A || 0x0001D50D <= code && code <= 0x0001D514 + + else + 0x0001D516 <= code && code <= 0x0001D51C || 0x0001D51E <= code && code <= 0x0001D539 || 0x0001D53B <= code && code <= 0x0001D53E || 0x0001D540 <= code && code <= 0x0001D544 || code == 0x0001D546 || 0x0001D54A <= code && code <= 0x0001D550 || 0x0001D552 <= code && code <= 0x0001D6A5 || 0x0001D6A8 <= code && code <= 0x0001D6C0 || 0x0001D6C2 <= code && code <= 0x0001D6DA || 0x0001D6DC <= code && code <= 0x0001D6FA || 0x0001D6FC <= code && code <= 0x0001D714 + + else if code < 0x0001E0FF then + 0x0001D716 <= code && code <= 0x0001D734 || 0x0001D736 <= code && code <= 0x0001D74E || 0x0001D750 <= code && code <= 0x0001D76E || 0x0001D770 <= code && code <= 0x0001D788 || 0x0001D78A <= code && code <= 0x0001D7A8 || 0x0001D7AA <= code && code <= 0x0001D7C2 || 0x0001D7C4 <= code && code <= 0x0001D7CB || 0x0001D7CE <= code && code <= 0x0001D7FF || 0x0001DF00 <= code && code <= 0x0001DF1E || 0x0001DF25 <= code && code <= 0x0001DF2A || 0x0001E030 <= code && code <= 0x0001E06D + + else + 0x0001E100 <= code && code <= 0x0001E12C || 0x0001E137 <= code && code <= 0x0001E13D || 0x0001E140 <= code && code <= 0x0001E149 || code == 0x0001E14E || 0x0001E290 <= code && code <= 0x0001E2AD || 0x0001E2C0 <= code && code <= 0x0001E2EB || 0x0001E2F0 <= code && code <= 0x0001E2F9 || 0x0001E4D0 <= code && code <= 0x0001E4EB || 0x0001E4F0 <= code && code <= 0x0001E4F9 || 0x0001E7E0 <= code && code <= 0x0001E7E6 || 0x0001E7E8 <= code && code <= 0x0001E7EB || 0x0001E7ED <= code && code <= 0x0001E7EE + + else if code < 0x0001EE60 then + if code < 0x0001EDFF then + 0x0001E7F0 <= code && code <= 0x0001E7FE || 0x0001E800 <= code && code <= 0x0001E8C4 || 0x0001E8C7 <= code && code <= 0x0001E8CF || 0x0001E900 <= code && code <= 0x0001E943 || code == 0x0001E94B || 0x0001E950 <= code && code <= 0x0001E959 || 0x0001EC71 <= code && code <= 0x0001ECAB || 0x0001ECAD <= code && code <= 0x0001ECAF || 0x0001ECB1 <= code && code <= 0x0001ECB4 || 0x0001ED01 <= code && code <= 0x0001ED2D || 0x0001ED2F <= code && code <= 0x0001ED3D + + else + 0x0001EE00 <= code && code <= 0x0001EE03 || 0x0001EE05 <= code && code <= 0x0001EE1F || 0x0001EE21 <= code && code <= 0x0001EE22 || code == 0x0001EE24 || code == 0x0001EE27 || 0x0001EE29 <= code && code <= 0x0001EE32 || 0x0001EE34 <= code && code <= 0x0001EE37 || code == 0x0001EE42 || 0x0001EE4D <= code && code <= 0x0001EE4F || 0x0001EE51 <= code && code <= 0x0001EE52 || code == 0x0001EE54 || modBy 2 code == 1 && (0x0001EE39 <= code && code <= 0x0001EE3B || 0x0001EE47 <= code && code <= 0x0001EE4B || 0x0001EE57 <= code && code <= 0x0001EE5F) + + else if code < 0x0001EEAA then + 0x0001EE61 <= code && code <= 0x0001EE62 || code == 0x0001EE64 || 0x0001EE67 <= code && code <= 0x0001EE6A || 0x0001EE6C <= code && code <= 0x0001EE72 || 0x0001EE74 <= code && code <= 0x0001EE77 || 0x0001EE79 <= code && code <= 0x0001EE7C || code == 0x0001EE7E || 0x0001EE80 <= code && code <= 0x0001EE89 || 0x0001EE8B <= code && code <= 0x0001EE9B || 0x0001EEA1 <= code && code <= 0x0001EEA3 || 0x0001EEA5 <= code && code <= 0x0001EEA9 + + else + 0x0001EEAB <= code && code <= 0x0001EEBB || 0x0001F100 <= code && code <= 0x0001F10C || 0x0001FBF0 <= code && code <= 0x0001FBF9 || 0x00020000 <= code && code <= 0x0002A6DF || 0x0002A700 <= code && code <= 0x0002B739 || 0x0002B740 <= code && code <= 0x0002B81D || 0x0002B820 <= code && code <= 0x0002CEA1 || 0x0002CEB0 <= code && code <= 0x0002EBE0 || 0x0002EBF0 <= code && code <= 0x0002EE5D || 0x0002F800 <= code && code <= 0x0002FA1D || 0x00030000 <= code && code <= 0x0003134A || 0x00031350 <= code && code <= 0x000323AF + ) charCodeIsLower : Int -> Bool @@ -25,3 +365,20 @@ charCodeIsUpper code = charCodeIsDigit : Int -> Bool charCodeIsDigit code = code <= 0x39 && 0x30 <= code + + +{-| Some code points like 🔧 are represented as 2 consecutive UTF-16 codes +within js strings. + +So when we use `String.slice`, the resulting String might only contain +one of these halves which are called surrogates. + +To check for that, the only way to tell whether you've encountered +a surrogate (that I can imagine at least) is by (ab)using that Char.toCode +accesses it's first _2_ indexes if the code at the first index indicates there must be a second half, +leading to NaN being returned. + +-} +isUtf16Surrogate : Char -> Bool +isUtf16Surrogate c = + Basics.isNaN (Basics.toFloat (Char.toCode c)) diff --git a/src/Elm/Parser/Base.elm b/src/Elm/Parser/Base.elm index 673aa0e2..de129f3d 100644 --- a/src/Elm/Parser/Base.elm +++ b/src/Elm/Parser/Base.elm @@ -1,25 +1,25 @@ module Elm.Parser.Base exposing (moduleName) -import Elm.Parser.Node as Node import Elm.Parser.Tokens as Tokens import Elm.Syntax.ModuleName exposing (ModuleName) -import Elm.Syntax.Node exposing (Node) +import Elm.Syntax.Node exposing (Node(..)) import ParserFast moduleName : ParserFast.Parser (Node ModuleName) moduleName = - ParserFast.map2 (\head tail -> head :: tail) + ParserFast.map2WithRange + (\range head tail -> + Node range (head :: tail) + ) Tokens.typeName moduleNameOrEmpty - |> Node.parserCore moduleNameOrEmpty : ParserFast.Parser ModuleName moduleNameOrEmpty = - ParserFast.orSucceed - (ParserFast.map2 (\head tail -> head :: tail) - (ParserFast.symbolFollowedBy "." Tokens.typeName) - (ParserFast.lazy (\() -> moduleNameOrEmpty)) - ) + ParserFast.map2OrSucceed + (\head tail -> head :: tail) + (ParserFast.symbolFollowedBy "." Tokens.typeName) + (ParserFast.lazy (\() -> moduleNameOrEmpty)) [] diff --git a/src/Elm/Parser/Comments.elm b/src/Elm/Parser/Comments.elm index 3f6a2945..1315dad4 100644 --- a/src/Elm/Parser/Comments.elm +++ b/src/Elm/Parser/Comments.elm @@ -1,28 +1,31 @@ -module Elm.Parser.Comments exposing (declarationDocumentation, moduleDocumentation, multilineCommentString, singleLineCommentCore) +module Elm.Parser.Comments exposing (declarationDocumentation, moduleDocumentation, multilineComment, singleLineComment) -import Elm.Parser.Node as Node +import Char.Extra import Elm.Syntax.Documentation exposing (Documentation) -import Elm.Syntax.Node exposing (Node) +import Elm.Syntax.Node exposing (Node(..)) import ParserFast exposing (Parser) -singleLineCommentCore : ParserFast.Parser String -singleLineCommentCore = +singleLineComment : ParserFast.Parser (Node String) +singleLineComment = ParserFast.symbolFollowedBy "--" - (ParserFast.chompWhile (\c -> c /= '\u{000D}' && c /= '\n')) - |> ParserFast.getChompedString + (ParserFast.whileMap + (\c -> c /= '\u{000D}' && c /= '\n' && not (Char.Extra.isUtf16Surrogate c)) + (\content -> "--" ++ content) + ) + |> ParserFast.mapWithRange Node -multilineCommentString : ParserFast.Parser String -multilineCommentString = +multilineComment : ParserFast.Parser (Node String) +multilineComment = ParserFast.offsetSourceAndThen (\offset source -> - case source |> String.slice offset (offset + 3) of - "{-|" -> + case String.slice (offset + 2) (offset + 3) source of + "|" -> problemUnexpectedDocumentation _ -> - multiLineCommentStringNoCheck + multiLineCommentNoCheck ) @@ -31,10 +34,11 @@ problemUnexpectedDocumentation = ParserFast.problem "unexpected documentation comment" -multiLineCommentStringNoCheck : Parser String -multiLineCommentStringNoCheck = - ParserFast.nestableMultiComment "{-" "-}" - |> ParserFast.getChompedString +multiLineCommentNoCheck : Parser (Node String) +multiLineCommentNoCheck = + ParserFast.nestableMultiCommentMapWithRange Node + ( '{', "-" ) + ( '-', "}" ) moduleDocumentation : Parser (Node String) @@ -47,17 +51,4 @@ declarationDocumentation = -- technically making the whole parser fail on multi-line comments would be "correct" -- but in practice, all declaration comments allow layout before which already handles -- these. - -- Here's how a "safe" version would look: - -- ParserFast.oneOf - --[ -- if the next symbol isn't "{-|", we commit to failure - -- (ParserFast.symbol "{-" |> ParserFast.ignore ParserFast.chompIf (\c -> c /= '|')) - -- |> ParserFast.backtrackable - -- |> ParserFast.Extra.continueWith (ParserFast.problem "multiline comment should be documentation comment") - --, ParserFast.multiComment "{-" "-}" Nestable - -- |> ParserFast.getChompedString - -- |> Node.parserCore - --] - -- |> ParserFast.backtrackable - ParserFast.nestableMultiComment "{-" "-}" - |> ParserFast.getChompedString - |> Node.parserCore + multiLineCommentNoCheck diff --git a/src/Elm/Parser/Declarations.elm b/src/Elm/Parser/Declarations.elm index 65df12b9..4a023c98 100644 --- a/src/Elm/Parser/Declarations.elm +++ b/src/Elm/Parser/Declarations.elm @@ -3,7 +3,6 @@ module Elm.Parser.Declarations exposing (declaration) import Elm.Parser.Comments as Comments import Elm.Parser.Expression exposing (expression) import Elm.Parser.Layout as Layout -import Elm.Parser.Node as Node import Elm.Parser.Patterns as Patterns import Elm.Parser.Tokens as Tokens import Elm.Parser.TypeAnnotation as TypeAnnotation exposing (typeAnnotation, typeAnnotationNoFnExcludingTypedWithArguments) @@ -12,7 +11,7 @@ import Elm.Syntax.Expression exposing (Expression) import Elm.Syntax.Infix as Infix import Elm.Syntax.Node as Node exposing (Node(..)) import Elm.Syntax.Pattern exposing (Pattern) -import Elm.Syntax.Range exposing (Location) +import Elm.Syntax.Range exposing (Location, Range) import Elm.Syntax.Signature exposing (Signature) import Elm.Syntax.Type exposing (ValueConstructor) import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation) @@ -23,13 +22,12 @@ import Rope declaration : Parser (WithComments (Node Declaration)) declaration = - ParserFast.oneOf - [ functionDeclarationWithoutDocumentation - , declarationWithDocumentation - , typeOrTypeAliasDefinitionWithoutDocumentation - , portDeclarationWithoutDocumentation - , infixDeclaration - ] + ParserFast.oneOf5 + functionDeclarationWithoutDocumentation + declarationWithDocumentation + typeOrTypeAliasDefinitionWithoutDocumentation + portDeclarationWithoutDocumentation + infixDeclaration declarationWithDocumentation : Parser (WithComments (Node Declaration)) @@ -43,49 +41,41 @@ declarationWithDocumentation = in case afterDocumentation.syntax of FunctionDeclarationAfterDocumentation functionDeclarationAfterDocumentation -> - let - (Node startNameRange startName) = - functionDeclarationAfterDocumentation.startName - in case functionDeclarationAfterDocumentation.signature of Just signature -> let - (Node implementationNameRange implementationName) = + (Node implementationNameRange _) = signature.implementationName - in - if implementationName == startName ++ "" then - let - (Node expressionRange _) = - functionDeclarationAfterDocumentation.expression - in - { comments = afterDocumentation.comments - , syntax = - Node { start = start, end = expressionRange.end } - (Declaration.FunctionDeclaration - { documentation = Just documentation - , signature = - Just - (Node.combine Signature - functionDeclarationAfterDocumentation.startName - signature.typeAnnotation - ) - , declaration = - Node { start = implementationNameRange.start, end = expressionRange.end } - { name = signature.implementationName - , arguments = functionDeclarationAfterDocumentation.arguments - , expression = functionDeclarationAfterDocumentation.expression - } - } - ) - } - |> ParserFast.succeed - else - ParserFast.problem - ("Expected to find the declaration for " ++ startName ++ " but found " ++ implementationName) + (Node expressionRange _) = + functionDeclarationAfterDocumentation.expression + in + { comments = afterDocumentation.comments + , syntax = + Node { start = start, end = expressionRange.end } + (Declaration.FunctionDeclaration + { documentation = Just documentation + , signature = + Just + (Node.combine Signature + functionDeclarationAfterDocumentation.startName + signature.typeAnnotation + ) + , declaration = + Node { start = implementationNameRange.start, end = expressionRange.end } + { name = signature.implementationName + , arguments = functionDeclarationAfterDocumentation.arguments + , expression = functionDeclarationAfterDocumentation.expression + } + } + ) + } Nothing -> let + (Node startNameRange _) = + functionDeclarationAfterDocumentation.startName + (Node expressionRange _) = functionDeclarationAfterDocumentation.expression in @@ -104,7 +94,6 @@ declarationWithDocumentation = } ) } - |> ParserFast.succeed TypeDeclarationAfterDocumentation typeDeclarationAfterDocumentation -> let @@ -134,7 +123,6 @@ declarationWithDocumentation = } ) } - |> ParserFast.succeed TypeAliasDeclarationAfterDocumentation typeAliasDeclarationAfterDocumentation -> let @@ -152,7 +140,6 @@ declarationWithDocumentation = } ) } - |> ParserFast.succeed PortDeclarationAfterDocumentation portDeclarationAfterName -> let @@ -173,18 +160,44 @@ declarationWithDocumentation = } ) } - |> ParserFast.succeed ) Comments.declarationDocumentation (Layout.layoutStrictFollowedByWithComments - (ParserFast.oneOf - [ functionAfterDocumentation - , typeOrTypeAliasDefinitionAfterDocumentation - , portDeclarationAfterDocumentation - ] + (ParserFast.oneOf3 + functionAfterDocumentation + typeOrTypeAliasDefinitionAfterDocumentation + portDeclarationAfterDocumentation ) ) - |> ParserFast.andThen identity + |> ParserFast.validate + (\result -> + let + (Node _ decl) = + result.syntax + in + case decl of + Declaration.FunctionDeclaration letFunctionDeclaration -> + case letFunctionDeclaration.signature of + Nothing -> + True + + Just (Node _ signature) -> + let + (Node _ implementationName) = + implementation.name + + (Node _ implementation) = + letFunctionDeclaration.declaration + + (Node _ signatureName) = + signature.name + in + implementationName == signatureName ++ "" + + _ -> + True + ) + "Expected to find the same name for declaration and signature" type DeclarationAfterDocumentation @@ -235,15 +248,13 @@ functionAfterDocumentation = ParserFast.map6 (\startName commentsAfterStartName maybeSignature arguments commentsAfterEqual result -> { comments = - commentsAfterStartName - |> Rope.prependTo - (case maybeSignature of - Nothing -> - Rope.empty + (case maybeSignature of + Nothing -> + commentsAfterStartName - Just signature -> - signature.comments - ) + Just signature -> + commentsAfterStartName |> Rope.prependTo signature.comments + ) |> Rope.prependTo arguments.comments |> Rope.prependTo commentsAfterEqual |> Rope.prependTo result.comments @@ -257,30 +268,28 @@ functionAfterDocumentation = } ) -- infix declarations itself don't have documentation - (Node.parserCore Tokens.functionName) + Tokens.functionNameNode Layout.maybeLayout - (ParserFast.orSucceed - (ParserFast.map4 - (\commentsBeforeTypeAnnotation typeAnnotationResult implementationName afterImplementationName -> - Just - { comments = - commentsBeforeTypeAnnotation - |> Rope.prependTo typeAnnotationResult.comments - |> Rope.prependTo implementationName.comments - |> Rope.prependTo afterImplementationName - , syntax = - { implementationName = implementationName.syntax - , typeAnnotation = typeAnnotationResult.syntax - } + (ParserFast.map4OrSucceed + (\commentsBeforeTypeAnnotation typeAnnotationResult implementationName afterImplementationName -> + Just + { comments = + commentsBeforeTypeAnnotation + |> Rope.prependTo typeAnnotationResult.comments + |> Rope.prependTo implementationName.comments + |> Rope.prependTo afterImplementationName + , syntax = + { implementationName = implementationName.syntax + , typeAnnotation = typeAnnotationResult.syntax } - ) - (ParserFast.symbolFollowedBy ":" Layout.maybeLayout) - TypeAnnotation.typeAnnotation - (Layout.layoutStrictFollowedBy - (Node.parserCore Tokens.functionName) - ) - Layout.maybeLayout + } ) + (ParserFast.symbolFollowedBy ":" Layout.maybeLayout) + TypeAnnotation.typeAnnotation + (Layout.layoutStrictFollowedBy + Tokens.functionNameNode + ) + Layout.maybeLayout Nothing ) parameterPatternsEqual @@ -290,27 +299,21 @@ functionAfterDocumentation = functionDeclarationWithoutDocumentation : Parser (WithComments (Node Declaration)) functionDeclarationWithoutDocumentation = - ParserFast.map6 - (\((Node startNameRange startName) as startNameNode) commentsAfterStartName maybeSignature arguments commentsAfterEqual result -> + ParserFast.map6WithStartLocation + (\startNameStart startNameNode commentsAfterStartName maybeSignature arguments commentsAfterEqual result -> let allComments : Comments allComments = - commentsAfterStartName - |> Rope.prependTo - (case maybeSignature of - Nothing -> - Rope.empty - - Just signature -> - signature.comments - ) + (case maybeSignature of + Nothing -> + commentsAfterStartName + + Just signature -> + commentsAfterStartName |> Rope.prependTo signature.comments + ) |> Rope.prependTo arguments.comments |> Rope.prependTo commentsAfterEqual |> Rope.prependTo result.comments - - startNameStart : Location - startNameStart = - startNameRange.start in case maybeSignature of Nothing -> @@ -333,67 +336,85 @@ functionDeclarationWithoutDocumentation = } ) } - |> ParserFast.succeed Just signature -> let - (Node implementationNameRange implementationName) = + (Node implementationNameRange _) = signature.implementationName - in - if implementationName == startName ++ "" then - let - (Node expressionRange _) = - result.syntax - in - { comments = allComments - , syntax = - Node { start = startNameStart, end = expressionRange.end } - (Declaration.FunctionDeclaration - { documentation = Nothing - , signature = Just (Node.combine Signature startNameNode signature.typeAnnotation) - , declaration = - Node { start = implementationNameRange.start, end = expressionRange.end } - { name = signature.implementationName - , arguments = arguments.syntax - , expression = result.syntax - } - } - ) - } - |> ParserFast.succeed - else - ParserFast.problem - ("Expected to find the declaration for " ++ startName ++ " but found " ++ implementationName) + (Node expressionRange _) = + result.syntax + in + { comments = allComments + , syntax = + Node { start = startNameStart, end = expressionRange.end } + (Declaration.FunctionDeclaration + { documentation = Nothing + , signature = Just (Node.combine Signature startNameNode signature.typeAnnotation) + , declaration = + Node { start = implementationNameRange.start, end = expressionRange.end } + { name = signature.implementationName + , arguments = arguments.syntax + , expression = result.syntax + } + } + ) + } ) - (Node.parserCore Tokens.functionNameNotInfix) + Tokens.functionNameNotInfixNode Layout.maybeLayout - (ParserFast.orSucceed - (ParserFast.map4 - (\commentsBeforeTypeAnnotation typeAnnotationResult implementationName afterImplementationName -> - Just - { comments = - commentsBeforeTypeAnnotation - |> Rope.prependTo typeAnnotationResult.comments - |> Rope.prependTo implementationName.comments - |> Rope.prependTo afterImplementationName - , implementationName = implementationName.syntax - , typeAnnotation = typeAnnotationResult.syntax - } - ) - (ParserFast.symbolFollowedBy ":" Layout.maybeLayout) - TypeAnnotation.typeAnnotation - (Layout.layoutStrictFollowedBy - (Node.parserCore Tokens.functionName) - ) - Layout.maybeLayout + (ParserFast.map4OrSucceed + (\commentsBeforeTypeAnnotation typeAnnotationResult implementationName afterImplementationName -> + Just + { comments = + commentsBeforeTypeAnnotation + |> Rope.prependTo typeAnnotationResult.comments + |> Rope.prependTo implementationName.comments + |> Rope.prependTo afterImplementationName + , implementationName = implementationName.syntax + , typeAnnotation = typeAnnotationResult.syntax + } ) + (ParserFast.symbolFollowedBy ":" Layout.maybeLayout) + TypeAnnotation.typeAnnotation + (Layout.layoutStrictFollowedBy + Tokens.functionNameNode + ) + Layout.maybeLayout Nothing ) parameterPatternsEqual Layout.maybeLayout expression - |> ParserFast.andThen identity + |> ParserFast.validate + (\result -> + let + (Node _ decl) = + result.syntax + in + case decl of + Declaration.FunctionDeclaration letFunctionDeclaration -> + case letFunctionDeclaration.signature of + Nothing -> + True + + Just (Node _ signature) -> + let + (Node _ implementationName) = + implementation.name + + (Node _ implementation) = + letFunctionDeclaration.declaration + + (Node _ signatureName) = + signature.name + in + implementationName == signatureName ++ "" + + _ -> + True + ) + "Expected to find the same name for declaration and signature" parameterPatternsEqual : Parser (WithComments (List (Node Pattern))) @@ -412,8 +433,8 @@ parameterPatternsEqual = infixDeclaration : Parser (WithComments (Node Declaration)) infixDeclaration = - ParserFast.map9 - (\commentsAfterInfix direction commentsAfterDirection precedence commentsAfterPrecedence operator commentsAfterOperator commentsAfterEqual fn -> + ParserFast.map9WithRange + (\range commentsAfterInfix direction commentsAfterDirection precedence commentsAfterPrecedence operator commentsAfterOperator commentsAfterEqual fn -> { comments = commentsAfterInfix |> Rope.prependTo commentsAfterDirection @@ -421,35 +442,33 @@ infixDeclaration = |> Rope.prependTo commentsAfterOperator |> Rope.prependTo commentsAfterEqual , syntax = - Declaration.InfixDeclaration - { direction = direction, precedence = precedence, operator = operator, function = fn } + Node range + (Declaration.InfixDeclaration + { direction = direction, precedence = precedence, operator = operator, function = fn } + ) } ) (ParserFast.keywordFollowedBy "infix" Layout.maybeLayout) - (Node.parserCore infixDirection) + infixDirection Layout.maybeLayout - (Node.parserCore ParserFast.int) + (ParserFast.mapWithRange Node ParserFast.int) Layout.maybeLayout - (Node.parserCore - (ParserFast.map2 - (\prefixOperator () -> prefixOperator) - (ParserFast.symbolFollowedBy "(" Tokens.prefixOperatorToken) - Tokens.parensEnd + (ParserFast.mapWithRange Node + (ParserFast.symbolFollowedBy "(" Tokens.prefixOperatorToken + |> ParserFast.followedBySymbol ")" ) ) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "=") + (Layout.maybeLayout |> ParserFast.followedBySymbol "=") Layout.maybeLayout - (Node.parserCore Tokens.functionName) - |> Node.parser + Tokens.functionNameNode -infixDirection : ParserFast.Parser Infix.InfixDirection +infixDirection : ParserFast.Parser (Node Infix.InfixDirection) infixDirection = - ParserFast.oneOf - [ ParserFast.keyword "right" Infix.Right - , ParserFast.keyword "left" Infix.Left - , ParserFast.keyword "non" Infix.Non - ] + ParserFast.oneOf3 + (ParserFast.mapWithRange Node (ParserFast.keyword "right" Infix.Right)) + (ParserFast.mapWithRange Node (ParserFast.keyword "left" Infix.Left)) + (ParserFast.mapWithRange Node (ParserFast.keyword "non" Infix.Non)) portDeclarationAfterDocumentation : Parser (WithComments DeclarationAfterDocumentation) @@ -470,8 +489,8 @@ portDeclarationAfterDocumentation = } ) (ParserFast.keywordFollowedBy "port" Layout.maybeLayout) - (Node.parserCore Tokens.functionName) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy ":") + Tokens.functionNameNode + (Layout.maybeLayout |> ParserFast.followedBySymbol ":") Layout.maybeLayout typeAnnotation @@ -502,8 +521,8 @@ portDeclarationWithoutDocumentation = } ) (ParserFast.keywordFollowedBy "port" Layout.maybeLayout) - (Node.parserCore Tokens.functionName) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy ":") + Tokens.functionNameNode + (Layout.maybeLayout |> ParserFast.followedBySymbol ":") Layout.maybeLayout typeAnnotation @@ -542,7 +561,7 @@ typeAliasDefinitionAfterDocumentationAfterTypePrefix = } ) (ParserFast.keywordFollowedBy "alias" Layout.maybeLayout) - (Node.parserCore Tokens.typeName) + Tokens.typeNameNode Layout.maybeLayout typeGenericListEquals Layout.maybeLayout @@ -568,7 +587,7 @@ customTypeDefinitionAfterDocumentationAfterTypePrefix = } } ) - (Node.parserCore Tokens.typeName) + Tokens.typeNameNode Layout.maybeLayout typeGenericListEquals Layout.maybeLayout @@ -583,7 +602,7 @@ customTypeDefinitionAfterDocumentationAfterTypePrefix = , syntax = variantResult.syntax } ) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "|" |> ParserFast.backtrackable) + (Layout.maybeLayoutBacktrackable |> ParserFast.followedBySymbol "|") Layout.maybeLayout valueConstructor ) @@ -592,40 +611,33 @@ customTypeDefinitionAfterDocumentationAfterTypePrefix = typeOrTypeAliasDefinitionWithoutDocumentation : Parser (WithComments (Node Declaration.Declaration)) typeOrTypeAliasDefinitionWithoutDocumentation = - ParserFast.mapWithStartPosition - (\start result -> - { comments = result.comments - , syntax = - Node { start = start, end = result.end } - result.declaration - } - ) - (ParserFast.map2 - (\commentsAfterType afterStart -> - let - allComments : Comments - allComments = - commentsAfterType |> Rope.prependTo afterStart.comments - in - case afterStart.syntax of - TypeDeclarationWithoutDocumentation typeDeclarationAfterDocumentation -> - let - end : Location - end = - case typeDeclarationAfterDocumentation.tailVariantsReverse of - (Node range _) :: _ -> - range.end - - [] -> - let - (Node headVariantRange _) = - typeDeclarationAfterDocumentation.headVariant - in - headVariantRange.end - in - { comments = allComments - , declaration = - Declaration.CustomTypeDeclaration + ParserFast.map2WithStartLocation + (\start commentsAfterType afterStart -> + let + allComments : Comments + allComments = + commentsAfterType |> Rope.prependTo afterStart.comments + in + case afterStart.syntax of + TypeDeclarationWithoutDocumentation typeDeclarationAfterDocumentation -> + let + end : Location + end = + case typeDeclarationAfterDocumentation.tailVariantsReverse of + (Node range _) :: _ -> + range.end + + [] -> + let + (Node headVariantRange _) = + typeDeclarationAfterDocumentation.headVariant + in + headVariantRange.end + in + { comments = allComments + , syntax = + Node { start = start, end = end } + (Declaration.CustomTypeDeclaration { documentation = Nothing , name = typeDeclarationAfterDocumentation.name , generics = typeDeclarationAfterDocumentation.parameters @@ -633,30 +645,32 @@ typeOrTypeAliasDefinitionWithoutDocumentation = typeDeclarationAfterDocumentation.headVariant :: List.reverse typeDeclarationAfterDocumentation.tailVariantsReverse } - , end = end - } + ) + } - TypeAliasDeclarationWithoutDocumentation typeAliasDeclarationAfterDocumentation -> - let - (Node typeAnnotationRange _) = - typeAliasDeclarationAfterDocumentation.typeAnnotation - in - { comments = allComments - , declaration = - Declaration.AliasDeclaration + TypeAliasDeclarationWithoutDocumentation typeAliasDeclarationAfterDocumentation -> + let + (Node typeAnnotationRange _) = + typeAliasDeclarationAfterDocumentation.typeAnnotation + in + { comments = allComments + , syntax = + Node { start = start, end = typeAnnotationRange.end } + (Declaration.AliasDeclaration { documentation = Nothing , name = typeAliasDeclarationAfterDocumentation.name , generics = typeAliasDeclarationAfterDocumentation.parameters , typeAnnotation = typeAliasDeclarationAfterDocumentation.typeAnnotation } - , end = typeAnnotationRange.end - } - ) - (ParserFast.keywordFollowedBy "type" Layout.maybeLayout) - (ParserFast.oneOf2 - typeAliasDefinitionWithoutDocumentationAfterTypePrefix - customTypeDefinitionWithoutDocumentationAfterTypePrefix - ) + ) + } + ) + (ParserFast.keywordFollowedBy "type" + Layout.maybeLayout + ) + (ParserFast.oneOf2 + typeAliasDefinitionWithoutDocumentationAfterTypePrefix + customTypeDefinitionWithoutDocumentationAfterTypePrefix ) @@ -679,7 +693,7 @@ typeAliasDefinitionWithoutDocumentationAfterTypePrefix = } ) (ParserFast.keywordFollowedBy "alias" Layout.maybeLayout) - (Node.parserCore Tokens.typeName) + Tokens.typeNameNode Layout.maybeLayout typeGenericListEquals Layout.maybeLayout @@ -705,7 +719,7 @@ customTypeDefinitionWithoutDocumentationAfterTypePrefix = } } ) - (Node.parserCore Tokens.typeName) + Tokens.typeNameNode Layout.maybeLayout typeGenericListEquals Layout.maybeLayout @@ -720,7 +734,7 @@ customTypeDefinitionWithoutDocumentationAfterTypePrefix = , syntax = variantResult.syntax } ) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "|" |> ParserFast.backtrackable) + (Layout.maybeLayoutBacktrackable |> ParserFast.followedBySymbol "|") Layout.maybeLayout valueConstructor ) @@ -732,24 +746,24 @@ valueConstructor = ParserFast.map2 (\((Node nameRange _) as name) argumentsReverse -> let - fullEnd : Location - fullEnd = + fullRange : Range + fullRange = case argumentsReverse.syntax of (Node lastArgRange _) :: _ -> - lastArgRange.end + { start = nameRange.start, end = lastArgRange.end } [] -> - nameRange.end + nameRange in { comments = argumentsReverse.comments , syntax = - Node { start = nameRange.start, end = fullEnd } + Node fullRange { name = name , arguments = List.reverse argumentsReverse.syntax } } ) - (Node.parserCore Tokens.typeName) + Tokens.typeNameNode (ParserWithComments.manyWithoutReverse (ParserFast.map2 (\commentsBefore typeAnnotationResult -> @@ -757,7 +771,7 @@ valueConstructor = , syntax = typeAnnotationResult.syntax } ) - (Layout.maybeLayout |> ParserFast.backtrackable) + Layout.maybeLayoutBacktrackable typeAnnotationNoFnExcludingTypedWithArguments ) ) @@ -772,6 +786,6 @@ typeGenericListEquals = , syntax = name } ) - (Node.parserCore Tokens.functionName) + Tokens.functionNameNode Layout.maybeLayout ) diff --git a/src/Elm/Parser/Expose.elm b/src/Elm/Parser/Expose.elm index 8549951d..6ecabc1e 100644 --- a/src/Elm/Parser/Expose.elm +++ b/src/Elm/Parser/Expose.elm @@ -1,33 +1,32 @@ module Elm.Parser.Expose exposing (exposeDefinition) import Elm.Parser.Layout as Layout -import Elm.Parser.Node as Node import Elm.Parser.Tokens as Tokens import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..)) import Elm.Syntax.Node exposing (Node(..)) import ParserFast exposing (Parser) import ParserWithComments exposing (WithComments) import Rope -import Set -exposeDefinition : Parser (WithComments Exposing) +exposeDefinition : Parser (WithComments (Node Exposing)) exposeDefinition = - ParserFast.map4 - (\commentsAfterExposing commentsBefore exposingListInnerResult () -> + ParserFast.map3WithRange + (\range commentsAfterExposing commentsBefore exposingListInnerResult -> { comments = commentsAfterExposing |> Rope.prependTo commentsBefore |> Rope.prependTo exposingListInnerResult.comments - , syntax = exposingListInnerResult.syntax + , syntax = Node range exposingListInnerResult.syntax } ) (ParserFast.symbolFollowedBy "exposing" - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "(") + (Layout.maybeLayout |> ParserFast.followedBySymbol "(") ) Layout.optimisticLayout - exposingListInner - Tokens.parensEnd + (exposingListInner + |> ParserFast.followedBySymbol ")" + ) exposingListInner : Parser (WithComments Exposing) @@ -54,11 +53,10 @@ exposingListInner = ) ) ) - (ParserFast.mapWithStartAndEndPosition - (\start commentsAfterDotDot end -> + (ParserFast.mapWithRange + (\range commentsAfterDotDot -> { comments = commentsAfterDotDot - , syntax = - All { start = start, end = end } + , syntax = All range } ) (ParserFast.symbolFollowedBy ".." Layout.maybeLayout) @@ -67,77 +65,72 @@ exposingListInner = exposable : Parser (WithComments (Node TopLevelExpose)) exposable = - ParserFast.oneOf - [ functionExpose - , typeExpose - , infixExpose - ] + ParserFast.oneOf3 + functionExpose + typeExpose + infixExpose infixExpose : ParserFast.Parser (WithComments (Node TopLevelExpose)) infixExpose = - ParserFast.map2 (\infixName () -> { comments = Rope.empty, syntax = InfixExpose infixName }) + ParserFast.map2WithRange + (\range infixName () -> + { comments = Rope.empty + , syntax = Node range (InfixExpose infixName) + } + ) (ParserFast.symbolFollowedBy "(" - (ParserFast.variable - { inner = \c -> c /= ')' - , reserved = Set.empty - , start = \c -> c /= ')' - } + (ParserFast.ifFollowedByWhileWithoutLinebreak + (\c -> c /= ')' && c /= '\n' && c /= ' ') + (\c -> c /= ')' && c /= '\n' && c /= ' ') ) ) Tokens.parensEnd - |> Node.parser typeExpose : Parser (WithComments (Node TopLevelExpose)) typeExpose = - ParserFast.map2 - (\typeName open -> + ParserFast.map2WithRange + (\range typeName open -> case open of Nothing -> - { comments = Rope.empty, syntax = TypeOrAliasExpose typeName } + { comments = Rope.empty + , syntax = Node range (TypeOrAliasExpose typeName) + } Just openRange -> { comments = openRange.comments - , syntax = - TypeExpose { name = typeName, open = Just openRange.syntax } + , syntax = Node range (TypeExpose { name = typeName, open = Just openRange.syntax }) } ) Tokens.typeName - (ParserFast.orSucceed - (ParserFast.map2 - (\commentsBefore all -> - Just - { comments = commentsBefore |> Rope.prependTo all.comments - , syntax = all.range - } + (ParserFast.map2OrSucceed + (\commentsBefore all -> + Just + { comments = commentsBefore |> Rope.prependTo all.comments + , syntax = all.range + } + ) + Layout.maybeLayoutBacktrackable + (ParserFast.map2WithRange + (\range left right -> + { comments = left |> Rope.prependTo right, range = range } ) - (Layout.maybeLayout |> ParserFast.backtrackable) - (ParserFast.mapWithStartAndEndPosition - (\start comments end -> - { comments = comments, range = { start = start, end = end } } - ) - (ParserFast.map2 (\left right -> left |> Rope.prependTo right) - (ParserFast.symbolFollowedBy "(" - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "..") - ) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy ")") - ) + (ParserFast.symbolFollowedBy "(" + (Layout.maybeLayout |> ParserFast.followedBySymbol "..") ) + (Layout.maybeLayout |> ParserFast.followedBySymbol ")") ) Nothing ) - |> Node.parser functionExpose : Parser (WithComments (Node TopLevelExpose)) functionExpose = - ParserFast.mapWithStartAndEndPosition - (\start name end -> + Tokens.functionNameMapWithRange + (\range name -> { comments = Rope.empty , syntax = - Node { start = start, end = end } - (FunctionExpose name) + Node range (FunctionExpose name) } ) - Tokens.functionName diff --git a/src/Elm/Parser/Expression.elm b/src/Elm/Parser/Expression.elm index 5add7524..ded47442 100644 --- a/src/Elm/Parser/Expression.elm +++ b/src/Elm/Parser/Expression.elm @@ -1,8 +1,6 @@ module Elm.Parser.Expression exposing (expression) import Elm.Parser.Layout as Layout -import Elm.Parser.Node as Node -import Elm.Parser.Numbers import Elm.Parser.Patterns as Patterns import Elm.Parser.Tokens as Tokens import Elm.Parser.TypeAnnotation as TypeAnnotation @@ -10,41 +8,56 @@ import Elm.Syntax.Expression as Expression exposing (Case, Expression(..), LetDe import Elm.Syntax.Infix as Infix import Elm.Syntax.Node as Node exposing (Node(..)) import Elm.Syntax.Pattern exposing (Pattern) -import Elm.Syntax.Range exposing (Location) +import Elm.Syntax.Range exposing (Range) import Elm.Syntax.Signature exposing (Signature) import ParserFast exposing (Parser) -import ParserFast.Advanced import ParserWithComments exposing (Comments, WithComments) import Rope subExpression : Parser (WithComments (Node Expression)) subExpression = - ParserFast.oneOf - [ qualifiedOrVariantOrRecordConstructorReferenceExpression - , unqualifiedFunctionReferenceExpression - , literalExpression - , numberExpression - , tupledExpression - , listOrGlslExpression - , recordExpression - , caseExpression - , lambdaExpression - , letExpression - , ifBlockExpression - , recordAccessFunctionExpression - , negationOperation - , charLiteralExpression - ] - - -andThenOneOf : List ( Int, Parser (WithComments ExtensionRight) ) -andThenOneOf = + ParserFast.oneOf14 + qualifiedOrVariantOrRecordConstructorReferenceExpressionFollowedByRecordAccess + unqualifiedFunctionReferenceExpressionFollowedByRecordAccess + literalExpression + numberExpression + tupledExpressionIfNecessaryFollowedByRecordAccess + listOrGlslExpression + recordExpressionFollowedByRecordAccess + caseExpression + lambdaExpression + letExpression + ifBlockExpression + recordAccessFunctionExpression + negationOperation + charLiteralExpression + + +multiRecordAccess : ParserFast.Parser (List (Node String)) +multiRecordAccess = + ParserFast.loopWhileSucceeds + (ParserFast.symbolFollowedBy "." Tokens.functionNameNode) + [] + (::) + List.reverse + + +multiRecordAccessMap : (List (Node String) -> res) -> ParserFast.Parser res +multiRecordAccessMap fieldsToRes = + ParserFast.loopWhileSucceeds + (ParserFast.symbolFollowedBy "." Tokens.functionNameNode) + [] + (::) + (\reversed -> fieldsToRes (List.reverse reversed)) + + +extensionRightByPrecedence : List ( Int, Parser (WithComments ExtensionRight) ) +extensionRightByPrecedence = -- TODO Add tests for all operators -- TODO Report a syntax error when encountering multiple of the comparison operators -- `a < b < c` is not valid Elm syntax - [ recordAccess - , infixLeft 1 (ParserFast.lazy (\() -> abovePrecedence1)) "|>" + [ infixLeft 1 (ParserFast.lazy (\() -> abovePrecedence1)) "|>" , infixRight 5 (ParserFast.lazy (\() -> abovePrecedence4)) "++" , infixRight 1 (ParserFast.lazy (\() -> abovePrecedence0)) "<|" , infixRight 9 (ParserFast.lazy (\() -> abovePrecedence8)) ">>" @@ -52,7 +65,7 @@ andThenOneOf = , infixLeft 7 (ParserFast.lazy (\() -> abovePrecedence7)) "*" , infixRight 5 (ParserFast.lazy (\() -> abovePrecedence4)) "::" , infixLeft 6 (ParserFast.lazy (\() -> abovePrecedence6)) "+" - , infixLeftSubtraction 6 (ParserFast.lazy (\() -> abovePrecedence6)) + , infixLeft 6 (ParserFast.lazy (\() -> abovePrecedence6)) "-" , infixLeft 6 (ParserFast.lazy (\() -> abovePrecedence6)) "|." , infixRight 3 (ParserFast.lazy (\() -> abovePrecedence2)) "&&" , infixLeft 5 (ParserFast.lazy (\() -> abovePrecedence5)) "|=" @@ -68,95 +81,42 @@ andThenOneOf = , infixLeft 8 (ParserFast.lazy (\() -> abovePrecedence8)) "" , infixNonAssociative 4 (ParserFast.lazy (\() -> abovePrecedence4)) "<" , infixRight 8 (ParserFast.lazy (\() -> abovePrecedence7)) "^" - - -- function application must be last - -- TODO validate function application arguments (issue #209) - , functionCall ] expression : Parser (WithComments (Node Expression)) expression = - subExpressionMap abovePrecedence0 - - -recordAccess : ( Int, Parser (WithComments ExtensionRight) ) -recordAccess = - postfix 100 recordAccessParser - - -recordAccessParser : Parser (WithComments ExtensionRight) -recordAccessParser = - lookBehindOneCharacterAndThen - (\c -> - if c == " " || c == "\n" || c == "\u{000D}" then - problemRecordAccessStartingWithSpace - - else - dotField - ) - - -problemRecordAccessStartingWithSpace : ParserFast.Parser a -problemRecordAccessStartingWithSpace = - ParserFast.problem "Record access can't start with a space" - - -dotField : ParserFast.Parser (WithComments ExtensionRight) -dotField = - ParserFast.symbolFollowedBy "." - (ParserFast.mapWithStartAndEndPosition - (\nameStart name nameEnd -> - { comments = Rope.empty - , syntax = - ExtendRightByRecordAccess - (Node { start = nameStart, end = nameEnd } name) - } - ) - Tokens.functionName - ) - - -functionCall : ( Int, Parser (WithComments ExtensionRight) ) -functionCall = - infixHelp 90 - (ParserFast.lazy (\() -> abovePrecedence90)) - Layout.positivelyIndentedFollowedBy - ExtendRightByApplication + extendedSubExpressionMap Basics.identity abovePrecedence0 glslExpressionAfterOpeningSquareBracket : Parser (WithComments (Node Expression)) glslExpressionAfterOpeningSquareBracket = ParserFast.symbolFollowedBy "glsl|" - (ParserFast.mapWithStartAndEndPosition - (\start s end -> + (ParserFast.mapWithRange + (\range s -> { comments = Rope.empty , syntax = Node -- TODO for v8: don't include extra end width (from bug in elm/parser) in range - { start = { row = start.row, column = start.column - 6 } - , end = { row = end.row, column = end.column + 2 } + { start = { row = range.start.row, column = range.start.column - 6 } + , end = { row = range.end.row, column = range.end.column + 2 } } (GLSLExpression s) } ) - (ParserFast.Advanced.loop "" untilGlslEnd) - ) - - -untilGlslEnd : String -> Parser (ParserFast.Advanced.Step String String) -untilGlslEnd soFar = - ParserFast.oneOf - [ ParserFast.symbol "|]" (ParserFast.Advanced.Done soFar) - , ParserFast.mapChompedString - (\beforeVerticalBar () -> - ParserFast.Advanced.Loop (soFar ++ beforeVerticalBar) - ) - (ParserFast.chompIfFollowedBy (\c -> c /= '|') - (ParserFast.chompWhile (\c -> c /= '|')) + (ParserFast.loopUntil + (ParserFast.symbol "|]" ()) + (ParserFast.oneOf2 + (ParserFast.symbol "|" "|") + (ParserFast.while (\c -> c /= '|')) + ) + "" + (\extension soFar -> + soFar ++ extension ++ "" + ) + identity ) - , ParserFast.symbol "|" (ParserFast.Advanced.Loop (soFar ++ "|")) - ] + ) listOrGlslExpression : Parser (WithComments (Node Expression)) @@ -168,44 +128,37 @@ expressionAfterOpeningSquareBracket : Parser (WithComments (Node Expression)) expressionAfterOpeningSquareBracket = ParserFast.oneOf2 glslExpressionAfterOpeningSquareBracket - (ParserFast.mapWithStartAndEndPosition - (\start elements end -> - { comments = elements.comments + (ParserFast.map2WithRange + (\range commentsBefore elements -> + { comments = commentsBefore |> Rope.prependTo elements.comments , syntax = Node - { start = { row = start.row, column = start.column - 1 } - , end = end + { start = { row = range.start.row, column = range.start.column - 1 } + , end = range.end } elements.syntax } ) - (ParserFast.map2 - (\commentsBefore elements -> - { comments = commentsBefore |> Rope.prependTo elements.comments - , syntax = elements.syntax - } - ) - Layout.maybeLayout - (ParserFast.oneOf2 - (ParserFast.symbol "]" { comments = Rope.empty, syntax = ListExpr [] }) - (ParserFast.map4 - (\head commentsAfterHead tail () -> - { comments = - head.comments - |> Rope.prependTo commentsAfterHead - |> Rope.prependTo tail.comments - , syntax = ListExpr (head.syntax :: tail.syntax) - } - ) - expression - Layout.maybeLayout - (ParserWithComments.many - (ParserFast.symbolFollowedBy "," - (Layout.maybeAroundBothSides expression) - ) + Layout.maybeLayout + (ParserFast.oneOf2 + (ParserFast.symbol "]" { comments = Rope.empty, syntax = ListExpr [] }) + (ParserFast.map3 + (\head commentsAfterHead tail -> + { comments = + head.comments + |> Rope.prependTo commentsAfterHead + |> Rope.prependTo tail.comments + , syntax = ListExpr (head.syntax :: tail.syntax) + } + ) + expression + Layout.maybeLayout + (ParserWithComments.many + (ParserFast.symbolFollowedBy "," + (Layout.maybeAroundBothSides expression) ) - Tokens.squareEnd ) + |> ParserFast.followedBySymbol "]" ) ) ) @@ -215,19 +168,40 @@ expressionAfterOpeningSquareBracket = -- recordExpression -recordExpression : Parser (WithComments (Node Expression)) -recordExpression = - ParserFast.map2 - (\commentsBefore afterCurly -> - { comments = - commentsBefore - |> Rope.prependTo afterCurly.comments - , syntax = afterCurly.syntax - } +recordExpressionFollowedByRecordAccess : Parser (WithComments (Node Expression)) +recordExpressionFollowedByRecordAccess = + ParserFast.symbolFollowedBy "{" + (ParserFast.map2 + (\leftestResult recordAccesses -> + case recordAccesses of + [] -> + leftestResult + + _ :: _ -> + { comments = leftestResult.comments + , syntax = + recordAccesses + |> List.foldl + (\((Node fieldRange _) as fieldNode) ((Node leftRange _) as leftNode) -> + Node { start = leftRange.start, end = fieldRange.end } + (Expression.RecordAccess leftNode fieldNode) + ) + leftestResult.syntax + } + ) + (ParserFast.map2WithRange + (\range commentsBefore afterCurly -> + { comments = + commentsBefore + |> Rope.prependTo afterCurly.comments + , syntax = Node (rangeMoveStartLeftByOneColumn range) afterCurly.syntax + } + ) + Layout.maybeLayout + recordContentsCurlyEnd + ) + multiRecordAccess ) - (ParserFast.symbolFollowedBy "{" Layout.maybeLayout) - recordContentsCurlyEnd - |> Node.parser recordContentsCurlyEnd : Parser (WithComments Expression) @@ -249,7 +223,7 @@ recordContentsCurlyEnd = RecordExpr (Node.combine Tuple.pair nameNode firstFieldValue :: tailFields.syntax) } ) - (Node.parserCore Tokens.functionName) + Tokens.functionNameNode Layout.maybeLayout (ParserFast.oneOf2 (ParserFast.map2 @@ -276,7 +250,7 @@ recordContentsCurlyEnd = ) ) recordFields - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "}") + (Layout.maybeLayout |> ParserFast.followedBySymbol "}") ) (ParserFast.symbol "}" { comments = Rope.empty, syntax = RecordExpr [] }) @@ -302,48 +276,43 @@ recordFields = recordSetterNodeWithLayout : Parser (WithComments (Node RecordSetter)) recordSetterNodeWithLayout = - Node.parser - (ParserFast.map5 - (\name commentsAfterFunctionName commentsAfterEquals expressionResult commentsAfterExpression -> - { comments = - commentsAfterFunctionName - |> Rope.prependTo commentsAfterEquals - |> Rope.prependTo expressionResult.comments - |> Rope.prependTo commentsAfterExpression - , syntax = - ( name, expressionResult.syntax ) - } - ) - (Node.parserCore Tokens.functionName) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "=") - Layout.maybeLayout - expression - -- This extra whitespace is just included for compatibility with earlier version - -- TODO for v8: remove - Layout.maybeLayout + ParserFast.map5WithRange + (\range name commentsAfterFunctionName commentsAfterEquals expressionResult commentsAfterExpression -> + { comments = + commentsAfterFunctionName + |> Rope.prependTo commentsAfterEquals + |> Rope.prependTo expressionResult.comments + |> Rope.prependTo commentsAfterExpression + , syntax = Node range ( name, expressionResult.syntax ) + } ) + Tokens.functionNameNode + (Layout.maybeLayout |> ParserFast.followedBySymbol "=") + Layout.maybeLayout + expression + -- This extra whitespace is just included for compatibility with earlier version + -- TODO for v8: remove + Layout.maybeLayout literalExpression : Parser (WithComments (Node Expression)) literalExpression = - ParserFast.mapWithStartAndEndPosition - (\start string end -> + Tokens.singleOrTripleQuotedStringLiteralMapWithRange + (\range string -> { comments = Rope.empty - , syntax = Node { start = start, end = end } (Literal string) + , syntax = Node range (Literal string) } ) - Tokens.singleOrTripleQuotedStringLiteral charLiteralExpression : Parser (WithComments (Node Expression)) charLiteralExpression = - ParserFast.mapWithStartAndEndPosition - (\start char end -> + Tokens.characterLiteralMapWithRange + (\range char -> { comments = Rope.empty - , syntax = Node { start = start, end = end } (CharLiteral char) + , syntax = Node range (CharLiteral char) } ) - Tokens.characterLiteral @@ -352,39 +321,36 @@ charLiteralExpression = lambdaExpression : Parser (WithComments (Node Expression)) lambdaExpression = - ParserFast.mapWithStartPosition - (\start lambda -> - let - (Node expressionRange _) = - lambda.expression - in - { comments = lambda.comments - , syntax = - Node { start = start, end = expressionRange.end } - (LambdaExpression - { args = lambda.args - , expression = lambda.expression - } - ) - } - ) - (ParserFast.map5 - (\commentsAfterBackslash firstArg commentsAfterFirstArg secondUpArgs expressionResult -> - { args = firstArg.syntax :: secondUpArgs.syntax - , comments = + ParserFast.symbolFollowedBy "\\" + (ParserFast.map5WithStartLocation + (\start commentsAfterBackslash firstArg commentsAfterFirstArg secondUpArgs expressionResult -> + let + (Node expressionRange _) = + expressionResult.syntax + in + { comments = commentsAfterBackslash |> Rope.prependTo firstArg.comments |> Rope.prependTo commentsAfterFirstArg |> Rope.prependTo secondUpArgs.comments |> Rope.prependTo expressionResult.comments - , expression = expressionResult.syntax + , syntax = + Node + { start = { row = start.row, column = start.column - 1 } + , end = expressionRange.end + } + (LambdaExpression + { args = firstArg.syntax :: secondUpArgs.syntax + , expression = expressionResult.syntax + } + ) } ) - (ParserFast.symbolFollowedBy "\\" Layout.maybeLayout) + Layout.maybeLayout Patterns.patternNotDirectlyComposing Layout.maybeLayout (ParserWithComments.until - Tokens.arrowRight + (ParserFast.symbol "->" ()) (ParserFast.map2 (\patternResult commentsAfter -> { comments = @@ -407,52 +373,45 @@ lambdaExpression = caseExpression : Parser (WithComments (Node Expression)) caseExpression = - ParserFast.mapWithStartPosition - (\start caseBlock -> - { comments = caseBlock.comments - , syntax = - Node - { start = start - , end = - case caseBlock.lastToSecondCase of - ( _, Node lastCaseExpressionRange _ ) :: _ -> - lastCaseExpressionRange.end - - [] -> - let - ( _, Node firstCaseExpressionRange _ ) = - caseBlock.firstCase - in - firstCaseExpressionRange.end - } - (CaseExpression - { expression = caseBlock.casedExpression - , cases = caseBlock.firstCase :: List.reverse caseBlock.lastToSecondCase - } - ) - } - ) - (ParserFast.map5 - (\commentsAfterCase casedExpressionResult commentsBeforeOf commentsAfterOf casesResult -> + ParserFast.keywordFollowedBy "case" + (ParserFast.map5WithStartLocation + (\start commentsAfterCase casedExpressionResult commentsBeforeOf commentsAfterOf casesResult -> let ( firstCase, lastToSecondCase ) = casesResult.syntax in - { casedExpression = casedExpressionResult.syntax - , comments = + { comments = commentsAfterCase |> Rope.prependTo casedExpressionResult.comments |> Rope.prependTo commentsBeforeOf |> Rope.prependTo commentsAfterOf |> Rope.prependTo casesResult.comments - , firstCase = firstCase - , lastToSecondCase = lastToSecondCase + , syntax = + Node + { start = { row = start.row, column = start.column - 4 } + , end = + case lastToSecondCase of + ( _, Node lastCaseExpressionRange _ ) :: _ -> + lastCaseExpressionRange.end + + [] -> + let + ( _, Node firstCaseExpressionRange _ ) = + firstCase + in + firstCaseExpressionRange.end + } + (CaseExpression + { expression = casedExpressionResult.syntax + , cases = firstCase :: List.reverse lastToSecondCase + } + ) } ) - (ParserFast.keywordFollowedBy "case" Layout.maybeLayout) + Layout.maybeLayout expression - (Layout.maybeLayoutUntilIgnored ParserFast.keywordFollowedBy "of") Layout.maybeLayout + (ParserFast.keywordFollowedBy "of" Layout.maybeLayout) (ParserFast.withIndentSetToColumn caseStatements) ) @@ -474,7 +433,7 @@ caseStatements = } ) Patterns.pattern - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "->") + (Layout.maybeLayout |> ParserFast.followedBySymbol "->") Layout.maybeLayout expression (ParserWithComments.manyWithoutReverse caseStatement) @@ -494,7 +453,7 @@ caseStatement = } ) Patterns.pattern - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "->") + (Layout.maybeLayout |> ParserFast.followedBySymbol "->") Layout.maybeLayout expression ) @@ -506,46 +465,43 @@ caseStatement = letExpression : Parser (WithComments (Node Expression)) letExpression = - ParserFast.mapWithStartPosition - (\start letBlock -> - let - (Node expressionRange _) = - letBlock.expression - in - { comments = letBlock.comments - , syntax = - Node { start = start, end = expressionRange.end } - (LetExpression - { declarations = letBlock.declarations - , expression = letBlock.expression - } - ) - } - ) - (ParserFast.map3 - (\declarations commentsAfterIn expressionResult -> + ParserFast.keywordFollowedBy "let" + (ParserFast.map3WithStartLocation + (\start declarations commentsAfterIn expressionResult -> + let + (Node expressionRange _) = + expressionResult.syntax + in { comments = declarations.comments |> Rope.prependTo commentsAfterIn |> Rope.prependTo expressionResult.comments - , declarations = declarations.syntax - , expression = expressionResult.syntax + , syntax = + Node + { start = { row = start.row, column = start.column - 3 } + , end = expressionRange.end + } + (LetExpression + { declarations = declarations.declarations + , expression = expressionResult.syntax + } + ) } ) - (ParserFast.withIndentSetToColumn + (ParserFast.withIndentSetToColumnMinus 3 (ParserFast.map2 (\commentsAfterLet declarations -> { comments = commentsAfterLet |> Rope.prependTo declarations.comments - , syntax = declarations.syntax + , declarations = declarations.syntax } ) - (ParserFast.keywordFollowedBy "let" Layout.maybeLayout) + Layout.maybeLayout (ParserFast.withIndentSetToColumn letDeclarationsIn) ) ) - -- check that the `in` token used as the end parser in letDeclarationsIn is indented correctly + -- checks that the `in` token used as the end parser in letDeclarationsIn is indented correctly (Layout.positivelyIndentedPlusFollowedBy 2 Layout.maybeLayout ) @@ -613,34 +569,28 @@ letDestructuringDeclaration = } ) Patterns.patternNotDirectlyComposing + (Layout.maybeLayout |> ParserFast.followedBySymbol "=") Layout.maybeLayout - (ParserFast.symbolFollowedBy "=" Layout.maybeLayout) expression letFunction : Parser (WithComments (Node LetDeclaration)) letFunction = - ParserFast.map6 - (\((Node startNameRange startName) as startNameNode) commentsAfterStartName maybeSignature arguments commentsAfterEqual expressionResult -> + ParserFast.map6WithStartLocation + (\startNameStart startNameNode commentsAfterStartName maybeSignature arguments commentsAfterEqual expressionResult -> let allComments : Comments allComments = - commentsAfterStartName - |> Rope.prependTo - (case maybeSignature of - Nothing -> - Rope.empty + (case maybeSignature of + Nothing -> + commentsAfterStartName - Just signature -> - signature.comments - ) + Just signature -> + commentsAfterStartName |> Rope.prependTo signature.comments + ) |> Rope.prependTo arguments.comments |> Rope.prependTo commentsAfterEqual |> Rope.prependTo expressionResult.comments - - startNameStart : Location - startNameStart = - startNameRange.start in case maybeSignature of Nothing -> @@ -663,67 +613,85 @@ letFunction = } ) } - |> ParserFast.succeed Just signature -> let - (Node implementationNameRange implementationName) = + (Node implementationNameRange _) = signature.implementationName - in - if implementationName == startName ++ "" then - let - (Node expressionRange _) = - expressionResult.syntax - in - { comments = allComments - , syntax = - Node { start = startNameStart, end = expressionRange.end } - (LetFunction - { documentation = Nothing - , signature = Just (Node.combine Signature startNameNode signature.typeAnnotation) - , declaration = - Node { start = implementationNameRange.start, end = expressionRange.end } - { name = signature.implementationName - , arguments = arguments.syntax - , expression = expressionResult.syntax - } - } - ) - } - |> ParserFast.succeed - else - ParserFast.problem - ("Expected to find the declaration for " ++ startName ++ " but found " ++ implementationName) + (Node expressionRange _) = + expressionResult.syntax + in + { comments = allComments + , syntax = + Node { start = startNameStart, end = expressionRange.end } + (LetFunction + { documentation = Nothing + , signature = Just (Node.combine Signature startNameNode signature.typeAnnotation) + , declaration = + Node { start = implementationNameRange.start, end = expressionRange.end } + { name = signature.implementationName + , arguments = arguments.syntax + , expression = expressionResult.syntax + } + } + ) + } ) - (Node.parserCore Tokens.functionName) + Tokens.functionNameNode Layout.maybeLayout - (ParserFast.orSucceed - (ParserFast.map4 - (\commentsBeforeTypeAnnotation typeAnnotationResult implementationName afterImplementationName -> - Just - { comments = - commentsBeforeTypeAnnotation - |> Rope.prependTo typeAnnotationResult.comments - |> Rope.prependTo implementationName.comments - |> Rope.prependTo afterImplementationName - , implementationName = implementationName.syntax - , typeAnnotation = typeAnnotationResult.syntax - } - ) - (ParserFast.symbolFollowedBy ":" Layout.maybeLayout) - TypeAnnotation.typeAnnotation - (Layout.layoutStrictFollowedBy - (Node.parserCore Tokens.functionName) - ) - Layout.maybeLayout + (ParserFast.map4OrSucceed + (\commentsBeforeTypeAnnotation typeAnnotationResult implementationName afterImplementationName -> + Just + { comments = + commentsBeforeTypeAnnotation + |> Rope.prependTo typeAnnotationResult.comments + |> Rope.prependTo implementationName.comments + |> Rope.prependTo afterImplementationName + , implementationName = implementationName.syntax + , typeAnnotation = typeAnnotationResult.syntax + } ) + (ParserFast.symbolFollowedBy ":" Layout.maybeLayout) + TypeAnnotation.typeAnnotation + (Layout.layoutStrictFollowedBy + Tokens.functionNameNode + ) + Layout.maybeLayout Nothing ) parameterPatternsEqual Layout.maybeLayout expression - |> ParserFast.andThen identity + |> ParserFast.validate + (\result -> + let + (Node _ letDeclaration) = + result.syntax + in + case letDeclaration of + LetDestructuring _ _ -> + True + + LetFunction letFunctionDeclaration -> + case letFunctionDeclaration.signature of + Nothing -> + True + + Just (Node _ signature) -> + let + (Node _ implementationName) = + implementation.name + + (Node _ implementation) = + letFunctionDeclaration.declaration + + (Node _ signatureName) = + signature.name + in + implementationName == signatureName ++ "" + ) + "Expected to find the same name for declaration and signature" parameterPatternsEqual : Parser (WithComments (List (Node Pattern))) @@ -742,39 +710,33 @@ parameterPatternsEqual = numberExpression : Parser (WithComments (Node Expression)) numberExpression = - ParserFast.mapWithStartAndEndPosition - (\start n end -> + ParserFast.floatOrIntOrHexMapWithRange + (\n range -> { comments = Rope.empty - , syntax = Node { start = start, end = end } n + , syntax = Node range (Floatable n) } ) - (Elm.Parser.Numbers.floatOrIntOrHex - Floatable - Integer - Hex + (\n range -> + { comments = Rope.empty + , syntax = Node range (Integer n) + } + ) + (\n range -> + { comments = Rope.empty + , syntax = Node range (Hex n) + } ) ifBlockExpression : Parser (WithComments (Node Expression)) ifBlockExpression = - ParserFast.mapWithStartPosition - (\start ifBlock -> - let - (Node ifFalseRange _) = - ifBlock.ifFalse - in - { comments = ifBlock.comments - , syntax = - Node { start = start, end = ifFalseRange.end } - (IfBlock - ifBlock.condition - ifBlock.ifTrue - ifBlock.ifFalse - ) - } - ) - (ParserFast.map8 - (\commentsAfterIf condition commentsBeforeThen commentsAfterThen ifTrue commentsBeforeElse commentsAfterElse ifFalse -> + ParserFast.keywordFollowedBy "if" + (ParserFast.map8WithStartLocation + (\start commentsAfterIf condition commentsBeforeThen commentsAfterThen ifTrue commentsBeforeElse commentsAfterElse ifFalse -> + let + (Node ifFalseRange _) = + ifFalse.syntax + in { comments = commentsAfterIf |> Rope.prependTo condition.comments @@ -784,147 +746,239 @@ ifBlockExpression = |> Rope.prependTo commentsBeforeElse |> Rope.prependTo commentsAfterElse |> Rope.prependTo ifFalse.comments - , condition = condition.syntax - , ifFalse = ifFalse.syntax - , ifTrue = ifTrue.syntax + , syntax = + Node + { start = { row = start.row, column = start.column - 2 } + , end = ifFalseRange.end + } + (IfBlock + condition.syntax + ifTrue.syntax + ifFalse.syntax + ) } ) - (ParserFast.keywordFollowedBy "if" Layout.maybeLayout) + Layout.maybeLayout expression - (Layout.maybeLayoutUntilIgnored ParserFast.keywordFollowedBy "then") Layout.maybeLayout + (ParserFast.keywordFollowedBy "then" Layout.maybeLayout) expression - (Layout.maybeLayoutUntilIgnored ParserFast.keywordFollowedBy "else") Layout.maybeLayout + (ParserFast.keywordFollowedBy "else" Layout.maybeLayout) expression ) negationOperation : Parser (WithComments (Node Expression)) negationOperation = - ParserFast.mapWithStartPosition - (\start subExpressionResult -> + ParserFast.symbolBacktrackableFollowedBy "-" + (ParserFast.offsetSourceAndThen + (\offset source -> + case String.slice (offset - 2) (offset - 1) source of + " " -> + negationAfterMinus + + -- not "\n" or "\r" since expressions are always indented + "(" -> + negationAfterMinus + + ")" -> + negationAfterMinus + + -- from the end of a multiline comment + "}" -> + negationAfterMinus + + -- TODO only for tests + "" -> + negationAfterMinus + + _ -> + negationWhitespaceProblem + ) + ) + + +negationWhitespaceProblem : Parser a +negationWhitespaceProblem = + ParserFast.problem "if a negation sign is not preceded by whitespace, it's considered subtraction" + + +negationAfterMinus : Parser (WithComments (Node Expression)) +negationAfterMinus = + ParserFast.map + (\subExpressionResult -> let (Node subExpressionRange _) = subExpressionResult.syntax in { comments = subExpressionResult.comments , syntax = - Node { start = start, end = subExpressionRange.end } + Node + { start = + { row = subExpressionRange.start.row + , column = subExpressionRange.start.column - 1 + } + , end = subExpressionRange.end + } (Negation subExpressionResult.syntax) } ) - (ParserFast.map2 - (\() subExpressionResult -> subExpressionResult) - (ParserFast.symbol "-" () |> ParserFast.backtrackable) - (extendedSubExpressionWithoutInitialLayout abovePrecedence95) - ) + (ParserFast.lazy (\() -> subExpression)) -qualifiedOrVariantOrRecordConstructorReferenceExpression : Parser (WithComments (Node Expression)) -qualifiedOrVariantOrRecordConstructorReferenceExpression = - ParserFast.mapWithStartAndEndPosition - (\start reference end -> +qualifiedOrVariantOrRecordConstructorReferenceExpressionFollowedByRecordAccess : Parser (WithComments (Node Expression)) +qualifiedOrVariantOrRecordConstructorReferenceExpressionFollowedByRecordAccess = + ParserFast.map2WithRange + (\range firstName after -> { comments = Rope.empty - , syntax = Node { start = start, end = end } reference - } - ) - (ParserFast.map2 - (\firstName after -> + , syntax = case after of Nothing -> - FunctionOrValue [] firstName - - Just ( qualificationAfter, unqualified ) -> - FunctionOrValue (firstName :: qualificationAfter) unqualified - ) - Tokens.typeName - maybeDotReferenceExpressionTuple - ) + Node range (FunctionOrValue [] firstName) + Just ( qualificationAfter, unqualified, recordAccesses ) -> + case recordAccesses of + [] -> + Node range (FunctionOrValue (firstName :: qualificationAfter) unqualified) -unqualifiedFunctionReferenceExpression : Parser (WithComments (Node Expression)) -unqualifiedFunctionReferenceExpression = - ParserFast.mapWithStartAndEndPosition - (\start unqualified end -> - { comments = Rope.empty - , syntax = - Node { start = start, end = end } - (FunctionOrValue [] unqualified) + (Node firstRecordAccessRange _) :: _ -> + let + referenceNode : Node Expression + referenceNode = + Node + { start = range.start + , end = + { row = firstRecordAccessRange.start.row + , column = firstRecordAccessRange.start.column - 1 + } + } + (FunctionOrValue (firstName :: qualificationAfter) unqualified) + in + recordAccesses + |> List.foldl + (\((Node fieldRange _) as fieldNode) ((Node leftRange _) as leftNode) -> + Node { start = leftRange.start, end = fieldRange.end } + (Expression.RecordAccess leftNode fieldNode) + ) + referenceNode } ) - Tokens.functionName + Tokens.typeName + maybeDotReferenceExpressionTuple -maybeDotReferenceExpressionTuple : ParserFast.Parser (Maybe ( List String, String )) +maybeDotReferenceExpressionTuple : ParserFast.Parser (Maybe ( List String, String, List (Node String) )) maybeDotReferenceExpressionTuple = ParserFast.orSucceed (ParserFast.symbolFollowedBy "." - (ParserFast.oneOf2 + (ParserFast.oneOf2Map + Just (ParserFast.map2 (\firstName after -> - Just - (case after of - Nothing -> - ( [], firstName ) + case after of + Nothing -> + ( [], firstName, [] ) - Just ( qualificationAfter, unqualified ) -> - ( firstName :: qualificationAfter, unqualified ) - ) + Just ( qualificationAfter, unqualified, recordAccess ) -> + ( firstName :: qualificationAfter, unqualified, recordAccess ) ) Tokens.typeName (ParserFast.lazy (\() -> maybeDotReferenceExpressionTuple)) ) - (ParserFast.map (\unqualified -> Just ( [], unqualified )) + Basics.identity + (ParserFast.map2 + (\name recordAccesses -> + Just ( [], name, recordAccesses ) + ) Tokens.functionName + multiRecordAccess ) ) ) Nothing +unqualifiedFunctionReferenceExpressionFollowedByRecordAccess : Parser (WithComments (Node Expression)) +unqualifiedFunctionReferenceExpressionFollowedByRecordAccess = + ParserFast.map2 + (\leftestResult recordAccesses -> + case recordAccesses of + [] -> + leftestResult + + _ :: _ -> + { comments = leftestResult.comments + , syntax = + recordAccesses + |> List.foldl + (\((Node fieldRange _) as fieldNode) ((Node leftRange _) as leftNode) -> + Node { start = leftRange.start, end = fieldRange.end } + (Expression.RecordAccess leftNode fieldNode) + ) + leftestResult.syntax + } + ) + (Tokens.functionNameMapWithRange + (\range unqualified -> + { comments = Rope.empty + , syntax = + Node range (FunctionOrValue [] unqualified) + } + ) + ) + multiRecordAccess + + recordAccessFunctionExpression : Parser (WithComments (Node Expression)) recordAccessFunctionExpression = - ParserFast.mapWithStartAndEndPosition - (\start field end -> - { comments = Rope.empty - , syntax = - Node { start = start, end = end } - (RecordAccessFunction ("." ++ field)) - } + ParserFast.symbolFollowedBy "." + (Tokens.functionNameMapWithRange + (\range field -> + { comments = Rope.empty + , syntax = + Node (range |> rangeMoveStartLeftByOneColumn) + (RecordAccessFunction ("." ++ field)) + } + ) ) - (ParserFast.symbolFollowedBy "." Tokens.functionName) -tupledExpression : Parser (WithComments (Node Expression)) -tupledExpression = +rangeMoveStartLeftByOneColumn : Range -> Range +rangeMoveStartLeftByOneColumn range = + { start = { row = range.start.row, column = range.start.column - 1 } + , end = range.end + } + + +tupledExpressionIfNecessaryFollowedByRecordAccess : Parser (WithComments (Node Expression)) +tupledExpressionIfNecessaryFollowedByRecordAccess = ParserFast.symbolFollowedBy "(" - (ParserFast.oneOf - (ParserFast.mapWithEndPosition - (\() end -> + (ParserFast.oneOf4 + (ParserFast.symbolWithEndLocation ")" + (\end -> { comments = Rope.empty , syntax = Node { start = { row = end.row, column = end.column - 2 }, end = end } UnitExpr } ) - (ParserFast.symbol ")" ()) - :: -- since `-` alone could indicate negation or prefix operator, - -- we check for `-)` first - ParserFast.mapWithEndPosition - (\() end -> - { comments = Rope.empty - , syntax = - Node { start = { row = end.row, column = end.column - 3 }, end = end } - expressionPrefixOperatorMinus - } - ) - (ParserFast.symbol "-)" ()) - :: tupledExpressionInnerAfterOpeningParens - -- and since prefix operators are much more rare than e.g. parenthesized - -- we check those later - :: allowedPrefixOperatorExceptMinusThenClosingParensOneOf ) + -- since `-` alone could indicate negation or prefix operator, + -- we check for `-)` first + (ParserFast.symbolWithEndLocation "-)" + (\end -> + { comments = Rope.empty + , syntax = + Node { start = { row = end.row, column = end.column - 3 }, end = end } + expressionPrefixOperatorMinus + } + ) + ) + tupledExpressionInnerAfterOpeningParens + -- and since prefix operators are much more rare than e.g. parenthesized + -- we check those later + (ParserFast.oneOf allowedPrefixOperatorExceptMinusThenClosingParensOneOf) ) @@ -944,191 +998,193 @@ allowedPrefixOperatorExceptMinusThenClosingParensOneOf = prefixOperatorLength = 2 + String.length allowedOperatorToken in - ParserFast.mapWithEndPosition - (\() end -> + ParserFast.symbolWithEndLocation + (allowedOperatorToken ++ ")") + (\end -> { comments = Rope.empty , syntax = Node { start = { row = end.row, column = end.column - prefixOperatorLength }, end = end } (PrefixOperator allowedOperatorToken) } ) - (ParserFast.symbol (allowedOperatorToken ++ ")") ()) ) tupledExpressionInnerAfterOpeningParens : Parser (WithComments (Node Expression)) tupledExpressionInnerAfterOpeningParens = - ParserFast.mapWithStartAndEndPosition - (\start tupled end -> - { comments = tupled.comments + ParserFast.map4WithRange + (\rangeAfterOpeningParens commentsBeforeFirstPart firstPart commentsAfterFirstPart tailParts -> + { comments = + commentsBeforeFirstPart + |> Rope.prependTo firstPart.comments + |> Rope.prependTo commentsAfterFirstPart + |> Rope.prependTo tailParts.comments , syntax = - Node { start = { row = start.row, column = start.column - 1 }, end = end } - tupled.syntax + case tailParts.syntax of + TupledParenthesizedFollowedByRecordAccesses recordAccesses -> + case recordAccesses of + [] -> + Node + { start = { row = rangeAfterOpeningParens.start.row, column = rangeAfterOpeningParens.start.column - 1 } + , end = rangeAfterOpeningParens.end + } + (ParenthesizedExpression firstPart.syntax) + + (Node firstRecordAccessRange _) :: _ -> + let + range : Range + range = + { start = { row = rangeAfterOpeningParens.start.row, column = rangeAfterOpeningParens.start.column - 1 } + , end = + { row = firstRecordAccessRange.start.row + , column = firstRecordAccessRange.start.column - 1 + } + } + + parenthesizedNode : Node Expression + parenthesizedNode = + Node range (ParenthesizedExpression firstPart.syntax) + in + recordAccesses + |> List.foldl + (\((Node fieldRange _) as fieldNode) ((Node leftRange _) as leftNode) -> + Node { start = leftRange.start, end = fieldRange.end } + (Expression.RecordAccess leftNode fieldNode) + ) + parenthesizedNode + + TupledTwoOrThree ( secondPart, maybeThirdPart ) -> + Node + { start = { row = rangeAfterOpeningParens.start.row, column = rangeAfterOpeningParens.start.column - 1 } + , end = rangeAfterOpeningParens.end + } + (case maybeThirdPart of + Nothing -> + TupledExpression [ firstPart.syntax, secondPart ] + + Just thirdPart -> + TupledExpression [ firstPart.syntax, secondPart, thirdPart ] + ) } ) - (ParserFast.map4 - (\commentsBeforeFirstPart firstPart commentsAfterFirstPart tailPartsReverse -> - case tailPartsReverse.syntax of - [] -> - { comments = - commentsBeforeFirstPart - |> Rope.prependTo firstPart.comments - |> Rope.prependTo commentsAfterFirstPart - , syntax = ParenthesizedExpression firstPart.syntax - } - - _ -> - { comments = - commentsBeforeFirstPart - |> Rope.prependTo firstPart.comments - |> Rope.prependTo commentsAfterFirstPart - |> Rope.prependTo tailPartsReverse.comments - , syntax = TupledExpression (firstPart.syntax :: List.reverse tailPartsReverse.syntax) - } + Layout.maybeLayout + expression + Layout.maybeLayout + (ParserFast.oneOf2 + (ParserFast.symbolFollowedBy ")" + (multiRecordAccessMap + (\recordAccesses -> { comments = Rope.empty, syntax = TupledParenthesizedFollowedByRecordAccesses recordAccesses }) + ) ) - Layout.maybeLayout - expression - Layout.maybeLayout - (ParserWithComments.untilWithoutReverse - Tokens.parensEnd - (ParserFast.map3 - (\commentsBefore partResult commentsAfter -> - { comments = - commentsBefore - |> Rope.prependTo partResult.comments - |> Rope.prependTo commentsAfter - , syntax = partResult.syntax - } + (ParserFast.map4 + (\commentsBefore partResult commentsAfter maybeThirdPart -> + { comments = + commentsBefore + |> Rope.prependTo partResult.comments + |> Rope.prependTo commentsAfter + |> Rope.prependTo maybeThirdPart.comments + , syntax = TupledTwoOrThree ( partResult.syntax, maybeThirdPart.syntax ) + } + ) + (ParserFast.symbolFollowedBy "," Layout.maybeLayout) + expression + Layout.maybeLayout + (ParserFast.oneOf2 + (ParserFast.symbol ")" { comments = Rope.empty, syntax = Nothing }) + (ParserFast.map3 + (\commentsBefore partResult commentsAfter -> + { comments = + commentsBefore + |> Rope.prependTo partResult.comments + |> Rope.prependTo commentsAfter + , syntax = Just partResult.syntax + } + ) + (ParserFast.symbolFollowedBy "," Layout.maybeLayout) + expression + Layout.maybeLayout + |> ParserFast.followedBySymbol ")" ) - (ParserFast.symbolFollowedBy "," Layout.maybeLayout) - expression - Layout.maybeLayout ) ) ) +type Tupled + = TupledParenthesizedFollowedByRecordAccesses (List (Node String)) + | TupledTwoOrThree ( Node Expression, Maybe (Node Expression) ) + + --- -subExpressionMap : - Parser (WithComments ExtensionRight) - -> Parser (WithComments (Node Expression)) -subExpressionMap aboveCurrentPrecedenceLayout = - let - step : - WithComments (Node Expression) - -> - Parser - (ParserFast.Advanced.Step - (WithComments (Node Expression)) - (WithComments (Node Expression)) - ) - step leftExpressionResult = - subExpressionLoopStep aboveCurrentPrecedenceLayout leftExpressionResult - in - ParserFast.map3 - (\commentsBefore leftExpressionResult commentsAfter -> +extendedSubExpressionMap : + (Node Expression -> res) + -> Parser (WithComments ExtensionRight) + -> Parser (WithComments res) +extendedSubExpressionMap expressionNodeoRes aboveCurrentPrecedenceLayout = + ParserFast.map5 + (\commentsBefore leftExpressionResult commentsBeforeExtension maybeArgsReverse extensionsRight -> + let + leftMaybeApplied : Node Expression + leftMaybeApplied = + case maybeArgsReverse.syntax of + [] -> + leftExpressionResult.syntax + + ((Node lastArgRange _) :: _) as argsReverse -> + let + ((Node leftRange _) as leftNode) = + leftExpressionResult.syntax + in + Node { start = leftRange.start, end = lastArgRange.end } + (Expression.Application + (leftNode :: List.reverse argsReverse) + ) + in { comments = commentsBefore |> Rope.prependTo leftExpressionResult.comments - |> Rope.prependTo commentsAfter - , syntax = leftExpressionResult.syntax - } - ) - Layout.optimisticLayout - (ParserFast.lazy (\() -> subExpression)) - Layout.optimisticLayout - |> ParserFast.andThen - (\leftExpression -> ParserFast.Advanced.loop leftExpression step) - - -extendedSubExpressionWithoutInitialLayout : - Parser (WithComments ExtensionRight) - -> Parser (WithComments (Node Expression)) -extendedSubExpressionWithoutInitialLayout aboveCurrentPrecedenceLayout = - let - step : - WithComments (Node Expression) - -> - Parser - (ParserFast.Advanced.Step - (WithComments (Node Expression)) - (WithComments (Node Expression)) - ) - step leftExpressionResult = - subExpressionLoopStep aboveCurrentPrecedenceLayout leftExpressionResult - in - ParserFast.map2 - (\leftExpressionResult commentsAfter -> - { comments = - leftExpressionResult.comments - |> Rope.prependTo commentsAfter - , syntax = leftExpressionResult.syntax + |> Rope.prependTo commentsBeforeExtension + |> Rope.prependTo maybeArgsReverse.comments + |> Rope.prependTo extensionsRight.comments + , syntax = + List.foldr applyExtensionRight + leftMaybeApplied + extensionsRight.syntax + |> expressionNodeoRes } ) + Layout.maybeLayout (ParserFast.lazy (\() -> subExpression)) Layout.optimisticLayout - |> ParserFast.andThen - (\leftExpression -> ParserFast.Advanced.loop leftExpression step) - - -subExpressionLoopStep : - Parser (WithComments ExtensionRight) - -> WithComments (Node Expression) - -> - Parser - (ParserFast.Advanced.Step - (WithComments (Node Expression)) - (WithComments (Node Expression)) - ) -subExpressionLoopStep aboveCurrentPrecedenceLayout leftExpressionResult = - ParserFast.orSucceed - (ParserFast.map2 - (\extensionRight commentsAfter -> - { comments = - leftExpressionResult.comments - |> Rope.prependTo extensionRight.comments - |> Rope.prependTo commentsAfter - , syntax = - leftExpressionResult.syntax - |> applyExtensionRight extensionRight.syntax - } - |> ParserFast.Advanced.Loop + (ParserWithComments.manyWithoutReverse + (ParserFast.map2 + (\arg commentsAfter -> + { comments = arg.comments |> Rope.prependTo commentsAfter + , syntax = arg.syntax + } + ) + (Layout.positivelyIndentedFollowedBy + (ParserFast.lazy (\() -> subExpression)) + ) + Layout.optimisticLayout ) + ) + (ParserWithComments.manyWithoutReverse aboveCurrentPrecedenceLayout - Layout.optimisticLayout ) - (ParserFast.Advanced.Done leftExpressionResult) applyExtensionRight : ExtensionRight -> Node Expression -> Node Expression -applyExtensionRight extensionRight ((Node { start } left) as leftNode) = - case extensionRight of - ExtendRightByRecordAccess ((Node { end } _) as field) -> - Node { start = start, end = end } - (Expression.RecordAccess leftNode field) - - ExtendRightByApplication ((Node { end } _) as right) -> - Node { start = start, end = end } - (Expression.Application - (case left of - Expression.Application (called :: firstArg :: secondArgUp) -> - called :: firstArg :: (secondArgUp ++ [ right ]) - - _ -> - [ leftNode, right ] - ) - ) - - ExtendRightByOperation extendRightOperation -> - let - ((Node { end } _) as right) = - extendRightOperation.expression - in - Node { start = start, end = end } - (OperatorApplication extendRightOperation.symbol extendRightOperation.direction leftNode right) +applyExtensionRight (ExtendRightByOperation extendRightOperation) ((Node { start } _) as leftNode) = + let + ((Node { end } _) as right) = + extendRightOperation.expression + in + Node { start = start, end = end } + (OperatorApplication extendRightOperation.symbol extendRightOperation.direction leftNode right) abovePrecedence0 : Parser (WithComments ExtensionRight) @@ -1176,19 +1232,9 @@ abovePrecedence9 = computeAbovePrecedence 9 -abovePrecedence90 : Parser (WithComments ExtensionRight) -abovePrecedence90 = - computeAbovePrecedence 90 - - -abovePrecedence95 : Parser (WithComments ExtensionRight) -abovePrecedence95 = - computeAbovePrecedence 95 - - computeAbovePrecedence : Int -> Parser (WithComments ExtensionRight) computeAbovePrecedence currentPrecedence = - andThenOneOf + extensionRightByPrecedence |> List.filterMap (\( precedence, parser ) -> if precedence > currentPrecedence then @@ -1233,62 +1279,20 @@ infixRight precedence possibilitiesForPrecedenceMinus1 symbol = ) -lookBehindOneCharacterAndThen : (String -> Parser res) -> Parser res -lookBehindOneCharacterAndThen callback = - ParserFast.offsetSourceAndThen - (\offset source -> - callback (String.slice (offset - 1) offset source) - ) - - -infixLeftSubtraction : Int -> Parser (WithComments ExtensionRight) -> ( Int, Parser (WithComments ExtensionRight) ) -infixLeftSubtraction precedence possibilitiesForPrecedence = - infixHelp precedence - possibilitiesForPrecedence - (\next -> - lookBehindOneCharacterAndThen - (\c -> - -- 'a-b', 'a - b' and 'a- b' are subtractions, but 'a -b' is an application on a negation - if c == " " || c == "\n" || c == "\u{000D}" then - Tokens.minusFollowedBySingleWhitespace next - - else - ParserFast.symbolFollowedBy "-" next - ) - ) - (\right -> - ExtendRightByOperation { symbol = "-", direction = Infix.Left, expression = right } - ) - - infixHelp : Int -> Parser (WithComments ExtensionRight) - -> (Parser (WithComments (Node Expression)) -> Parser (WithComments (Node Expression))) + -> (Parser (WithComments ExtensionRight) -> Parser (WithComments ExtensionRight)) -> (Node Expression -> ExtensionRight) -> ( Int, Parser (WithComments ExtensionRight) ) infixHelp leftPrecedence rightPrecedence operatorFollowedBy apply = ( leftPrecedence - , ParserFast.map - (\e -> - { comments = e.comments - , syntax = apply e.syntax - } - ) - (operatorFollowedBy - (subExpressionMap rightPrecedence) + , operatorFollowedBy + (extendedSubExpressionMap apply + rightPrecedence ) ) -postfix : Int -> Parser (WithComments ExtensionRight) -> ( Int, Parser (WithComments ExtensionRight) ) -postfix precedence operator = - ( precedence - , operator - ) - - type ExtensionRight = ExtendRightByOperation { symbol : String, direction : Infix.InfixDirection, expression : Node Expression } - | ExtendRightByApplication (Node Expression) - | ExtendRightByRecordAccess (Node String) diff --git a/src/Elm/Parser/File.elm b/src/Elm/Parser/File.elm index 0be9e170..2047eddb 100644 --- a/src/Elm/Parser/File.elm +++ b/src/Elm/Parser/File.elm @@ -5,7 +5,6 @@ import Elm.Parser.Declarations exposing (declaration) import Elm.Parser.Imports exposing (importDefinition) import Elm.Parser.Layout as Layout import Elm.Parser.Modules exposing (moduleDefinition) -import Elm.Parser.Node as Node import Elm.Syntax.Declaration exposing (Declaration) import Elm.Syntax.File exposing (File) import Elm.Syntax.Node exposing (Node) @@ -30,17 +29,15 @@ file = } ) (Layout.layoutStrictFollowedByWithComments - (Node.parser moduleDefinition) + moduleDefinition ) (Layout.layoutStrictFollowedByComments - (ParserFast.orSucceed - (ParserFast.map2 - (\moduleDocumentation commentsAfter -> - Rope.one moduleDocumentation |> Rope.filledPrependTo commentsAfter - ) - Comments.moduleDocumentation - Layout.layoutStrict + (ParserFast.map2OrSucceed + (\moduleDocumentation commentsAfter -> + Rope.one moduleDocumentation |> Rope.filledPrependTo commentsAfter ) + Comments.moduleDocumentation + Layout.layoutStrict Rope.empty ) ) diff --git a/src/Elm/Parser/Imports.elm b/src/Elm/Parser/Imports.elm index 4fd131cc..72af2c2b 100644 --- a/src/Elm/Parser/Imports.elm +++ b/src/Elm/Parser/Imports.elm @@ -3,105 +3,96 @@ module Elm.Parser.Imports exposing (importDefinition) import Elm.Parser.Base exposing (moduleName) import Elm.Parser.Expose exposing (exposeDefinition) import Elm.Parser.Layout as Layout -import Elm.Parser.Node as Node import Elm.Parser.Tokens as Tokens import Elm.Syntax.Import exposing (Import) import Elm.Syntax.Node exposing (Node(..)) import Elm.Syntax.Range exposing (Range) import ParserFast exposing (Parser) -import ParserWithComments exposing (WithComments) +import ParserWithComments exposing (Comments, WithComments) import Rope importDefinition : Parser (WithComments (Node Import)) importDefinition = - ParserFast.mapWithStartPosition - (\start importResult -> - { comments = importResult.comments - , syntax = - Node { start = start, end = importResult.end } - importResult.import_ - } - ) - (ParserFast.map6 - (\commentsAfterImport ((Node modRange _) as mod) commentsAfterModuleName maybeModuleAlias maybeExposingList commentsAfterEverything -> + ParserFast.map6WithStartLocation + (\start commentsAfterImport ((Node modRange _) as mod) commentsAfterModuleName maybeModuleAlias maybeExposingList commentsAfterEverything -> + let + endRange : Range + endRange = + case maybeModuleAlias of + Just moduleAliasValue -> + let + (Node range _) = + moduleAliasValue.syntax + in + range + + Nothing -> + case maybeExposingList of + Just exposingListValue -> + let + (Node range _) = + exposingListValue.syntax + in + range + + Nothing -> + modRange + in + { comments = let - endRange : Range - endRange = - case maybeModuleAlias of - Just moduleAliasValue -> - let - (Node range _) = - moduleAliasValue.syntax - in - range + commentsBeforeAlias : Comments + commentsBeforeAlias = + commentsAfterImport + |> Rope.prependTo commentsAfterModuleName + commentsBeforeExposingList : Comments + commentsBeforeExposingList = + case maybeModuleAlias of Nothing -> - case maybeExposingList of - Just exposingListValue -> - let - (Node range _) = - exposingListValue.syntax - in - range + commentsBeforeAlias - Nothing -> - modRange + Just moduleAliasValue -> + commentsBeforeAlias |> Rope.prependTo moduleAliasValue.comments in - { comments = - commentsAfterImport - |> Rope.prependTo commentsAfterModuleName - |> Rope.prependTo - (case maybeModuleAlias of - Nothing -> - Rope.empty + (case maybeExposingList of + Nothing -> + commentsBeforeExposingList - Just moduleAliasValue -> - moduleAliasValue.comments - ) - |> Rope.prependTo - (case maybeExposingList of - Nothing -> - Rope.empty - - Just exposingListValue -> - exposingListValue.comments - ) - |> Rope.prependTo commentsAfterEverything - , end = endRange.end - , import_ = + Just exposingListValue -> + commentsBeforeExposingList |> Rope.prependTo exposingListValue.comments + ) + |> Rope.prependTo commentsAfterEverything + , syntax = + Node { start = start, end = endRange.end } { moduleName = mod , moduleAlias = maybeModuleAlias |> Maybe.map .syntax , exposingList = maybeExposingList |> Maybe.map .syntax } - } + } + ) + (ParserFast.keywordFollowedBy "import" Layout.maybeLayout) + moduleName + Layout.optimisticLayout + (ParserFast.map3OrSucceed + (\commentsBefore moduleAliasNode commentsAfter -> + Just + { comments = commentsBefore |> Rope.prependTo commentsAfter + , syntax = moduleAliasNode + } ) - (ParserFast.keywordFollowedBy "import" Layout.maybeLayout) - moduleName - Layout.optimisticLayout - (ParserFast.orSucceed - (ParserFast.map3 - (\commentsBefore moduleAliasNode commentsAfter -> - Just - { comments = commentsBefore |> Rope.prependTo commentsAfter - , syntax = moduleAliasNode - } - ) - (ParserFast.keywordFollowedBy "as" Layout.maybeLayout) - (ParserFast.mapWithStartAndEndPosition - (\start moduleAlias end -> - Node { start = start, end = end } - [ moduleAlias ] - ) - Tokens.typeName - ) - Layout.optimisticLayout + (ParserFast.keywordFollowedBy "as" Layout.maybeLayout) + (Tokens.typeNameMapWithRange + (\range moduleAlias -> + Node range [ moduleAlias ] ) - Nothing - ) - (ParserFast.orSucceed - (Node.parserMapWithComments Just exposeDefinition) - Nothing ) Layout.optimisticLayout + Nothing + ) + (ParserFast.mapOrSucceed + Just + exposeDefinition + Nothing ) + Layout.optimisticLayout diff --git a/src/Elm/Parser/Layout.elm b/src/Elm/Parser/Layout.elm index 45f67eef..8f637697 100644 --- a/src/Elm/Parser/Layout.elm +++ b/src/Elm/Parser/Layout.elm @@ -5,7 +5,7 @@ module Elm.Parser.Layout exposing , layoutStrictFollowedByWithComments , maybeAroundBothSides , maybeLayout - , maybeLayoutUntilIgnored + , maybeLayoutBacktrackable , moduleLevelIndentationFollowedBy , onTopIndentationFollowedBy , optimisticLayout @@ -14,97 +14,31 @@ module Elm.Parser.Layout exposing ) import Elm.Parser.Comments as Comments -import Elm.Parser.Node as Node import ParserFast exposing (Parser) import ParserWithComments exposing (Comments, WithComments) import Rope -import Set - - -maybeLayoutUntilIgnored : (String -> Parser Comments -> Parser Comments) -> String -> Parser Comments -maybeLayoutUntilIgnored endParser endSymbol = - whitespaceAndCommentsUntilEndComments - (endParser endSymbol - (positivelyIndentedPlusFollowedBy (String.length endSymbol) - (ParserFast.succeed Rope.empty) - ) - ) - - -whitespaceAndCommentsUntilEndComments : Parser Comments -> Parser Comments -whitespaceAndCommentsUntilEndComments end = - let - fromSingleLineCommentUntilEnd : Parser Comments - fromSingleLineCommentUntilEnd = - ParserFast.map2 - (\content commentsAfter -> - Rope.one content - |> Rope.filledPrependTo commentsAfter - ) - (Node.parserCore Comments.singleLineCommentCore) - (ParserFast.lazy (\() -> whitespaceAndCommentsUntilEndComments end)) - - fromMultilineCommentNodeUntilEnd : Parser Comments - fromMultilineCommentNodeUntilEnd = - ParserFast.map2 - (\comment commentsAfter -> - Rope.one comment |> Rope.filledPrependTo commentsAfter - ) - (Node.parserCore Comments.multilineCommentString) - (ParserFast.lazy (\() -> whitespaceAndCommentsUntilEndComments end)) - - endOrFromCommentElseEmptyThenEnd : Parser Comments - endOrFromCommentElseEmptyThenEnd = - ParserFast.oneOf - [ end - , fromSingleLineCommentUntilEnd - , fromMultilineCommentNodeUntilEnd - ] - in - ParserFast.oneOf - [ whitespace - |> ParserFast.andThen (\_ -> endOrFromCommentElseEmptyThenEnd) - , end - , fromSingleLineCommentUntilEnd - , fromMultilineCommentNodeUntilEnd - ] whitespaceAndCommentsOrEmpty : Parser Comments whitespaceAndCommentsOrEmpty = - ParserFast.oneOf2 - (whitespace - -- whitespace can't be followed by more whitespace - |> ParserFast.andThen (\_ -> fromCommentElseEmpty) - ) - fromCommentElseEmpty - - -whitespace : Parser String -whitespace = - ParserFast.variable - { inner = \c -> c == ' ' || c == '\n' || c == '\u{000D}' - , reserved = Set.empty - , start = \c -> c == ' ' || c == '\n' || c == '\u{000D}' - } - - -fromCommentElseEmpty : Parser Comments -fromCommentElseEmpty = - -- since comments are comparatively rare - -- but expensive to check for, we allow shortcutting to dead end - ParserFast.offsetSourceAndThen - (\offset source -> - case source |> String.slice offset (offset + 2) of - "--" -> - -- this will always succeed from here, so no need to fall back to Rope.empty - fromSingleLineCommentNode - - "{-" -> - fromMultilineCommentNodeOrEmptyOnProblem - - _ -> - succeedRopeEmpty + ParserFast.chompWhileWhitespaceFollowedBy + -- whitespace can't be followed by more whitespace + -- + -- since comments are comparatively rare + -- but expensive to check for, we allow shortcutting + (ParserFast.offsetSourceAndThen + (\offset source -> + case source |> String.slice offset (offset + 2) of + "--" -> + -- this will always succeed from here, so no need to fall back to Rope.empty + fromSingleLineCommentNode + + "{-" -> + fromMultilineCommentNodeOrEmptyOnProblem + + _ -> + succeedRopeEmpty + ) ) @@ -115,17 +49,15 @@ succeedRopeEmpty = fromMultilineCommentNodeOrEmptyOnProblem : Parser Comments fromMultilineCommentNodeOrEmptyOnProblem = - ParserFast.orSucceed fromMultilineCommentNode Rope.empty - - -fromMultilineCommentNode : Parser Comments -fromMultilineCommentNode = - ParserFast.map2 + ParserFast.map2OrSucceed (\comment commentsAfter -> Rope.one comment |> Rope.filledPrependTo commentsAfter ) - (Node.parserCore Comments.multilineCommentString) - whitespaceAndCommentsOrEmpty + (Comments.multilineComment + |> ParserFast.followedByChompWhileWhitespace + ) + whitespaceAndCommentsOrEmptyLoop + Rope.empty fromSingleLineCommentNode : Parser Comments @@ -134,14 +66,49 @@ fromSingleLineCommentNode = (\content commentsAfter -> Rope.one content |> Rope.filledPrependTo commentsAfter ) - (Node.parserCore Comments.singleLineCommentCore) - whitespaceAndCommentsOrEmpty + (Comments.singleLineComment + |> ParserFast.followedByChompWhileWhitespace + ) + whitespaceAndCommentsOrEmptyLoop + + +whitespaceAndCommentsOrEmptyLoop : Parser Comments +whitespaceAndCommentsOrEmptyLoop = + ParserFast.loopWhileSucceeds + (ParserFast.oneOf2 + Comments.singleLineComment + Comments.multilineComment + |> ParserFast.followedByChompWhileWhitespace + ) + Rope.empty + (\right soFar -> soFar |> Rope.prependToFilled (Rope.one right)) + identity maybeLayout : Parser Comments maybeLayout = - whitespaceAndCommentsOrEmpty - |> ParserFast.ignore (positivelyIndentedFollowedBy (ParserFast.succeed ())) + whitespaceAndCommentsOrEmpty |> endsPositivelyIndented + + +maybeLayoutBacktrackable : Parser Comments +maybeLayoutBacktrackable = + whitespaceAndCommentsOrEmpty |> endsPositivelyIndentedBacktrackable + + +endsPositivelyIndented : Parser a -> Parser a +endsPositivelyIndented parser = + ParserFast.validateEndColumnIndentation + (\column indent -> column > indent) + "must be positively indented" + parser + + +endsPositivelyIndentedBacktrackable : Parser a -> Parser a +endsPositivelyIndentedBacktrackable parser = + ParserFast.validateEndColumnIndentationBacktrackable + (\column indent -> column > indent) + "must be positively indented" + parser {-| Check that the indentation of an already parsed token @@ -215,9 +182,7 @@ layoutStrictFollowedBy nextParser = layoutStrict : Parser Comments layoutStrict = - ParserFast.map2 (\commentsBefore () -> commentsBefore) - optimisticLayout - (onTopIndentationFollowedBy (ParserFast.succeed ())) + optimisticLayout |> endsTopIndented moduleLevelIndentationFollowedBy : Parser a -> Parser a @@ -237,6 +202,14 @@ problemModuleLevelIndentation = ParserFast.problem "must be on module-level indentation" +endsTopIndented : Parser a -> Parser a +endsTopIndented parser = + ParserFast.validateEndColumnIndentation + (\column indent -> column - indent == 0) + "must be on top indentation" + parser + + onTopIndentationFollowedBy : Parser a -> Parser a onTopIndentationFollowedBy nextParser = ParserFast.columnIndentAndThen diff --git a/src/Elm/Parser/Modules.elm b/src/Elm/Parser/Modules.elm index b4aa5657..419d4e75 100644 --- a/src/Elm/Parser/Modules.elm +++ b/src/Elm/Parser/Modules.elm @@ -3,23 +3,21 @@ module Elm.Parser.Modules exposing (moduleDefinition) import Elm.Parser.Base exposing (moduleName) import Elm.Parser.Expose exposing (exposeDefinition) import Elm.Parser.Layout as Layout -import Elm.Parser.Node as Node import Elm.Parser.Tokens as Tokens import Elm.Syntax.Module exposing (Module(..)) -import Elm.Syntax.Node exposing (Node) +import Elm.Syntax.Node exposing (Node(..)) import List.Extra import ParserFast exposing (Parser) import ParserWithComments exposing (WithComments) import Rope -moduleDefinition : Parser (WithComments Module) +moduleDefinition : Parser (WithComments (Node Module)) moduleDefinition = - ParserFast.oneOf - [ normalModuleDefinition - , portModuleDefinition - , effectModuleDefinition - ] + ParserFast.oneOf3 + normalModuleDefinition + portModuleDefinition + effectModuleDefinition effectWhereClause : Parser (WithComments ( String, Node String )) @@ -31,35 +29,46 @@ effectWhereClause = } ) Tokens.functionName - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "=") + (Layout.maybeLayout |> ParserFast.followedBySymbol "=") Layout.maybeLayout - (Node.parserCore Tokens.typeName) + Tokens.typeNameNode whereBlock : Parser (WithComments { command : Maybe (Node String), subscription : Maybe (Node String) }) whereBlock = - ParserFast.map2 - (\pairs () -> - { comments = pairs.comments - , syntax = - { command = - pairs.syntax - |> List.Extra.find (\( fnName, _ ) -> fnName == "command") - |> Maybe.map Tuple.second - , subscription = - pairs.syntax - |> List.Extra.find (\( fnName, _ ) -> fnName == "subscription") - |> Maybe.map Tuple.second + ParserFast.symbolFollowedBy "{" + (ParserFast.map4 + (\commentsBeforeHead head commentsAfterHead tail -> + let + pairs : List ( String, Node String ) + pairs = + head.syntax :: tail.syntax + in + { comments = + commentsBeforeHead + |> Rope.prependTo head.comments + |> Rope.prependTo commentsAfterHead + |> Rope.prependTo tail.comments + , syntax = + { command = + pairs + |> List.Extra.find (\( fnName, _ ) -> fnName == "command") + |> Maybe.map Tuple.second + , subscription = + pairs + |> List.Extra.find (\( fnName, _ ) -> fnName == "subscription") + |> Maybe.map Tuple.second + } } - } - ) - (ParserFast.symbolFollowedBy "{" - (ParserWithComments.sepBy1 - "," - (Layout.maybeAroundBothSides effectWhereClause) + ) + Layout.maybeLayout + effectWhereClause + Layout.maybeLayout + (ParserWithComments.many + (ParserFast.symbolFollowedBy "," (Layout.maybeAroundBothSides effectWhereClause)) ) ) - Tokens.curlyEnd + |> ParserFast.followedBySymbol "}" effectWhereClauses : Parser (WithComments { command : Maybe (Node String), subscription : Maybe (Node String) }) @@ -74,10 +83,10 @@ effectWhereClauses = whereBlock -effectModuleDefinition : Parser (WithComments Module) +effectModuleDefinition : Parser (WithComments (Node Module)) effectModuleDefinition = - ParserFast.map7 - (\commentsAfterEffect commentsAfterModule name commentsAfterName whereClauses commentsAfterWhereClauses exp -> + ParserFast.map7WithRange + (\range commentsAfterEffect commentsAfterModule name commentsAfterName whereClauses commentsAfterWhereClauses exp -> { comments = commentsAfterEffect |> Rope.prependTo commentsAfterModule @@ -86,12 +95,14 @@ effectModuleDefinition = |> Rope.prependTo commentsAfterWhereClauses |> Rope.prependTo exp.comments , syntax = - EffectModule - { moduleName = name - , exposingList = exp.syntax - , command = whereClauses.syntax.command - , subscription = whereClauses.syntax.subscription - } + Node range + (EffectModule + { moduleName = name + , exposingList = exp.syntax + , command = whereClauses.syntax.command + , subscription = whereClauses.syntax.subscription + } + ) } ) (ParserFast.keywordFollowedBy "effect" Layout.maybeLayout) @@ -100,44 +111,48 @@ effectModuleDefinition = Layout.maybeLayout effectWhereClauses Layout.maybeLayout - (Node.parser exposeDefinition) + exposeDefinition -normalModuleDefinition : Parser (WithComments Module) +normalModuleDefinition : Parser (WithComments (Node Module)) normalModuleDefinition = - ParserFast.map4 - (\commentsAfterModule moduleName commentsAfterModuleName exposingList -> + ParserFast.map4WithRange + (\range commentsAfterModule moduleName commentsAfterModuleName exposingList -> { comments = commentsAfterModule |> Rope.prependTo commentsAfterModuleName |> Rope.prependTo exposingList.comments , syntax = - NormalModule - { moduleName = moduleName - , exposingList = exposingList.syntax - } + Node range + (NormalModule + { moduleName = moduleName + , exposingList = exposingList.syntax + } + ) } ) (ParserFast.keywordFollowedBy "module" Layout.maybeLayout) moduleName Layout.maybeLayout - (Node.parser exposeDefinition) + exposeDefinition -portModuleDefinition : Parser (WithComments Module) +portModuleDefinition : Parser (WithComments (Node Module)) portModuleDefinition = - ParserFast.map5 - (\commentsAfterPort commentsAfterModule moduleName commentsAfterModuleName exposingList -> + ParserFast.map5WithRange + (\range commentsAfterPort commentsAfterModule moduleName commentsAfterModuleName exposingList -> { comments = commentsAfterPort |> Rope.prependTo commentsAfterModule |> Rope.prependTo commentsAfterModuleName |> Rope.prependTo exposingList.comments - , syntax = PortModule { moduleName = moduleName, exposingList = exposingList.syntax } + , syntax = + Node range + (PortModule { moduleName = moduleName, exposingList = exposingList.syntax }) } ) (ParserFast.keywordFollowedBy "port" Layout.maybeLayout) (ParserFast.keywordFollowedBy "module" Layout.maybeLayout) moduleName Layout.maybeLayout - (Node.parser exposeDefinition) + exposeDefinition diff --git a/src/Elm/Parser/Node.elm b/src/Elm/Parser/Node.elm index 900eac8e..d4e6d3d3 100644 --- a/src/Elm/Parser/Node.elm +++ b/src/Elm/Parser/Node.elm @@ -1,43 +1,16 @@ -module Elm.Parser.Node exposing (parser, parserCore, parserMapWithComments) +module Elm.Parser.Node exposing (parser) import Elm.Syntax.Node exposing (Node(..)) import ParserFast exposing (Parser) import ParserWithComments exposing (WithComments) -parserMapWithComments : (WithComments (Node a) -> b) -> Parser (WithComments a) -> Parser b -parserMapWithComments valueNodeChange p = - ParserFast.mapWithStartAndEndPosition - (\start v end -> - { comments = v.comments - , syntax = - Node - { start = start - , end = end - } - v.syntax - } - |> valueNodeChange - ) - p - - parser : Parser (WithComments a) -> Parser (WithComments (Node a)) parser p = - ParserFast.mapWithStartAndEndPosition - (\start v end -> + ParserFast.mapWithRange + (\range v -> { comments = v.comments - , syntax = - Node { start = start, end = end } v.syntax + , syntax = Node range v.syntax } ) p - - -parserCore : ParserFast.Parser a -> ParserFast.Parser (Node a) -parserCore p = - ParserFast.mapWithStartAndEndPosition - (\start v end -> - Node { start = start, end = end } v - ) - p diff --git a/src/Elm/Parser/Numbers.elm b/src/Elm/Parser/Numbers.elm deleted file mode 100644 index f907212b..00000000 --- a/src/Elm/Parser/Numbers.elm +++ /dev/null @@ -1,25 +0,0 @@ -module Elm.Parser.Numbers exposing (floatOrIntOrHex, intOrHex) - -import ParserFast - - -floatOrIntOrHex : (Float -> a) -> (Int -> a) -> (Int -> a) -> ParserFast.Parser a -floatOrIntOrHex floatf intf hexf = - ParserFast.number - { binary = Nothing - , float = Just floatf - , hex = Just hexf - , int = Just intf - , octal = Nothing - } - - -intOrHex : (Int -> a) -> (Int -> a) -> ParserFast.Parser a -intOrHex intf hexf = - ParserFast.number - { binary = Nothing - , float = Nothing - , hex = Just hexf - , int = Just intf - , octal = Nothing - } diff --git a/src/Elm/Parser/Patterns.elm b/src/Elm/Parser/Patterns.elm index 4840b78e..500d49f5 100644 --- a/src/Elm/Parser/Patterns.elm +++ b/src/Elm/Parser/Patterns.elm @@ -1,8 +1,6 @@ module Elm.Parser.Patterns exposing (pattern, patternNotDirectlyComposing) import Elm.Parser.Layout as Layout -import Elm.Parser.Node as Node -import Elm.Parser.Numbers import Elm.Parser.Tokens as Tokens import Elm.Syntax.Node as Node exposing (Node(..)) import Elm.Syntax.Pattern as Pattern exposing (Pattern(..)) @@ -49,16 +47,17 @@ composablePatternTryToCompose = maybeComposedWith : Parser { comments : ParserWithComments.Comments, syntax : PatternComposedWith } maybeComposedWith = - ParserFast.oneOf - [ ParserFast.map2 + ParserFast.oneOf2OrSucceed + (ParserFast.map2 (\commentsAfterAs name -> { comments = commentsAfterAs , syntax = PatternComposedWithAs name } ) (ParserFast.keywordFollowedBy "as" Layout.maybeLayout) - (Node.parserCore Tokens.functionName) - , ParserFast.map2 + Tokens.functionNameNode + ) + (ParserFast.map2 (\commentsAfterCons patternResult -> { comments = patternResult.comments |> Rope.prependTo commentsAfterCons , syntax = PatternComposedWithCons patternResult.syntax @@ -66,18 +65,18 @@ maybeComposedWith = ) (ParserFast.symbolFollowedBy "::" Layout.maybeLayout) pattern - , ParserFast.succeed { comments = Rope.empty, syntax = PatternComposedWithNothing () } - ] + ) + { comments = Rope.empty, syntax = PatternComposedWithNothing () } parensPattern : Parser (WithComments (Node Pattern)) parensPattern = - ParserFast.map2 - (\commentsBeforeHead contentResult -> + ParserFast.map2WithRange + (\range commentsBeforeHead contentResult -> { comments = commentsBeforeHead |> Rope.prependTo contentResult.comments - , syntax = contentResult.syntax + , syntax = Node range contentResult.syntax } ) (ParserFast.symbolFollowedBy "(" Layout.maybeLayout) @@ -109,58 +108,53 @@ parensPattern = ) (ParserFast.symbol ")" { comments = Rope.empty, syntax = UnitPattern }) ) - |> Node.parser varPattern : Parser (WithComments (Node Pattern)) varPattern = - Tokens.functionName - |> ParserFast.mapWithStartAndEndPosition - (\start var end -> - { comments = Rope.empty - , syntax = - Node { start = start, end = end } (VarPattern var) - } - ) + Tokens.functionNameMapWithRange + (\range var -> + { comments = Rope.empty + , syntax = Node range (VarPattern var) + } + ) numberPart : Parser (WithComments (Node Pattern)) numberPart = - Elm.Parser.Numbers.intOrHex - (\n -> { comments = Rope.empty, syntax = IntPattern n }) - (\n -> { comments = Rope.empty, syntax = HexPattern n }) - |> Node.parser + ParserFast.intOrHexMapWithRange + (\n range -> { comments = Rope.empty, syntax = Node range (IntPattern n) }) + (\n range -> { comments = Rope.empty, syntax = Node range (HexPattern n) }) charPattern : Parser (WithComments (Node Pattern)) charPattern = - Tokens.characterLiteral - |> ParserFast.mapWithStartAndEndPosition - (\start char end -> - { comments = Rope.empty, syntax = Node { start = start, end = end } (CharPattern char) } - ) + Tokens.characterLiteralMapWithRange + (\range char -> + { comments = Rope.empty, syntax = Node range (CharPattern char) } + ) listPattern : Parser (WithComments (Node Pattern)) listPattern = - ParserFast.map2 - (\commentsBeforeElements maybeElements -> + ParserFast.map2WithRange + (\range commentsBeforeElements maybeElements -> case maybeElements of Nothing -> { comments = commentsBeforeElements - , syntax = patternListEmpty + , syntax = Node range patternListEmpty } Just elements -> { comments = commentsBeforeElements |> Rope.prependTo elements.comments - , syntax = ListPattern elements.syntax + , syntax = Node range (ListPattern elements.syntax) } ) (ParserFast.symbolFollowedBy "[" Layout.maybeLayout) (ParserFast.oneOf2 (ParserFast.symbol "]" Nothing) - (ParserFast.map4 - (\head commentsAfterHead tail () -> + (ParserFast.map3 + (\head commentsAfterHead tail -> Just { comments = head.comments @@ -176,10 +170,9 @@ listPattern = (Layout.maybeAroundBothSides pattern) ) ) - Tokens.squareEnd + |> ParserFast.followedBySymbol "]" ) ) - |> Node.parser patternListEmpty : Pattern @@ -189,93 +182,97 @@ patternListEmpty = composablePattern : Parser (WithComments (Node Pattern)) composablePattern = - ParserFast.oneOf - [ varPattern - , qualifiedPatternWithConsumeArgs - , allPattern - , unitPattern - , parensPattern - , recordPattern - , stringPattern - , listPattern - , numberPart - , charPattern - ] + ParserFast.oneOf10 + varPattern + qualifiedPatternWithConsumeArgs + allPattern + unitPattern + parensPattern + recordPattern + stringPattern + listPattern + numberPart + charPattern patternNotDirectlyComposing : Parser (WithComments (Node Pattern)) patternNotDirectlyComposing = - ParserFast.oneOf - [ varPattern - , qualifiedPatternWithoutConsumeArgs - , allPattern - , unitPattern - , parensPattern - , recordPattern - , stringPattern - , listPattern - , numberPart - , charPattern - ] + ParserFast.oneOf10 + varPattern + qualifiedPatternWithoutConsumeArgs + allPattern + unitPattern + parensPattern + recordPattern + stringPattern + listPattern + numberPart + charPattern allPattern : Parser (WithComments (Node Pattern)) allPattern = - ParserFast.symbol "_" { comments = Rope.empty, syntax = AllPattern } - |> Node.parser + ParserFast.symbolWithRange "_" + (\range -> + { comments = Rope.empty + , syntax = Node range AllPattern + } + ) unitPattern : Parser (WithComments (Node Pattern)) unitPattern = - ParserFast.symbol "()" { comments = Rope.empty, syntax = UnitPattern } - |> Node.parser + ParserFast.symbolWithRange "()" + (\range -> + { comments = Rope.empty + , syntax = Node range UnitPattern + } + ) stringPattern : Parser (WithComments (Node Pattern)) stringPattern = - Tokens.singleOrTripleQuotedStringLiteral - |> ParserFast.mapWithStartAndEndPosition - (\start string end -> - { comments = Rope.empty - , syntax = - Node { start = start, end = end } (StringPattern string) - } - ) + Tokens.singleOrTripleQuotedStringLiteralMapWithRange + (\range string -> + { comments = Rope.empty + , syntax = Node range (StringPattern string) + } + ) maybeDotTypeNamesTuple : ParserFast.Parser (Maybe ( List String, String )) maybeDotTypeNamesTuple = - ParserFast.orSucceed - (ParserFast.map2 - (\startName afterStartName -> - case afterStartName of - Nothing -> - Just ( [], startName ) + ParserFast.map2OrSucceed + (\startName afterStartName -> + case afterStartName of + Nothing -> + Just ( [], startName ) - Just ( qualificationAfter, unqualified ) -> - Just ( startName :: qualificationAfter, unqualified ) - ) - (ParserFast.symbolFollowedBy "." Tokens.typeName) - (ParserFast.lazy (\() -> maybeDotTypeNamesTuple)) + Just ( qualificationAfter, unqualified ) -> + Just ( startName :: qualificationAfter, unqualified ) ) + (ParserFast.symbolFollowedBy "." Tokens.typeName) + (ParserFast.lazy (\() -> maybeDotTypeNamesTuple)) Nothing qualifiedPatternWithConsumeArgs : Parser (WithComments (Node Pattern)) qualifiedPatternWithConsumeArgs = - ParserFast.map3 - (\startName afterStartName args -> + ParserFast.map3WithRange + (\range startName afterStartName args -> { comments = args.comments , syntax = - NamedPattern - (case afterStartName of - Nothing -> - { moduleName = [], name = startName } - - Just ( qualificationAfter, unqualified ) -> - { moduleName = startName :: qualificationAfter, name = unqualified } + Node range + (NamedPattern + (case afterStartName of + Nothing -> + { moduleName = [], name = startName } + + Just ( qualificationAfter, unqualified ) -> + { moduleName = startName :: qualificationAfter, name = unqualified } + ) + args.syntax ) - args.syntax } ) Tokens.typeName @@ -287,55 +284,56 @@ qualifiedPatternWithConsumeArgs = , syntax = arg.syntax } ) - (Layout.maybeLayout |> ParserFast.backtrackable) + Layout.maybeLayoutBacktrackable patternNotDirectlyComposing ) ) - |> Node.parser qualifiedPatternWithoutConsumeArgs : Parser (WithComments (Node Pattern)) qualifiedPatternWithoutConsumeArgs = - ParserFast.mapWithStartAndEndPosition - (\start name end -> + ParserFast.map2WithRange + (\range firstName after -> { comments = Rope.empty , syntax = - Node { start = start, end = end } (NamedPattern name []) + Node range + (NamedPattern + (case after of + Nothing -> + { moduleName = [], name = firstName } + + Just ( qualificationAfter, unqualified ) -> + { moduleName = firstName :: qualificationAfter, name = unqualified } + ) + [] + ) } ) - (ParserFast.map2 - (\firstName after -> - case after of - Nothing -> - { moduleName = [], name = firstName } - - Just ( qualificationAfter, unqualified ) -> - { moduleName = firstName :: qualificationAfter, name = unqualified } - ) - Tokens.typeName - maybeDotTypeNamesTuple - ) + Tokens.typeName + maybeDotTypeNamesTuple recordPattern : Parser (WithComments (Node Pattern)) recordPattern = - ParserFast.map2 - (\commentsBeforeElements maybeElements -> + ParserFast.map2WithRange + (\range commentsBeforeElements maybeElements -> case maybeElements of Nothing -> { comments = commentsBeforeElements - , syntax = patternRecordEmpty + , syntax = Node range patternRecordEmpty } Just elements -> { comments = commentsBeforeElements |> Rope.prependTo elements.comments - , syntax = RecordPattern elements.syntax + , syntax = + Node range + (RecordPattern elements.syntax) } ) (ParserFast.symbolFollowedBy "{" Layout.maybeLayout) (ParserFast.oneOf2 - (ParserFast.map4 - (\head commentsAfterHead tail () -> + (ParserFast.map3 + (\head commentsAfterHead tail -> Just { comments = commentsAfterHead @@ -343,7 +341,7 @@ recordPattern = , syntax = head :: tail.syntax } ) - (Node.parserCore Tokens.functionName) + Tokens.functionNameNode Layout.maybeLayout (ParserWithComments.many (ParserFast.map3 @@ -353,15 +351,14 @@ recordPattern = } ) (ParserFast.symbolFollowedBy "," Layout.maybeLayout) - (Node.parserCore Tokens.functionName) + Tokens.functionNameNode Layout.maybeLayout ) ) - Tokens.curlyEnd + |> ParserFast.followedBySymbol "}" ) (ParserFast.symbol "}" Nothing) ) - |> Node.parser patternRecordEmpty : Pattern diff --git a/src/Elm/Parser/Tokens.elm b/src/Elm/Parser/Tokens.elm index d0ed9a65..cce36f40 100644 --- a/src/Elm/Parser/Tokens.elm +++ b/src/Elm/Parser/Tokens.elm @@ -1,59 +1,81 @@ module Elm.Parser.Tokens exposing ( inToken - , squareEnd, curlyEnd, arrowRight, equal, parensEnd - , minusFollowedBySingleWhitespace + , equal, parensEnd , prefixOperatorToken, allowedOperatorTokens - , characterLiteral, singleOrTripleQuotedStringLiteral - , functionName, functionNameNotInfix, typeName + , characterLiteralMapWithRange, singleOrTripleQuotedStringLiteralMapWithRange + , functionName, functionNameNode, functionNameMapWithRange, functionNameNotInfixNode, typeName, typeNameNode, typeNameMapWithRange ) {-| @docs inToken -@docs squareEnd, curlyEnd, arrowRight, equal, parensEnd -@docs minusFollowedBySingleWhitespace +@docs equal, parensEnd @docs prefixOperatorToken, allowedOperatorTokens -@docs characterLiteral, singleOrTripleQuotedStringLiteral -@docs functionName, functionNameNotInfix, typeName +@docs characterLiteralMapWithRange, singleOrTripleQuotedStringLiteralMapWithRange +@docs functionName, functionNameNode, functionNameMapWithRange, functionNameNotInfixNode, typeName, typeNameNode, typeNameMapWithRange -} import Char import Char.Extra -import Hex +import Elm.Syntax.Node exposing (Node(..)) +import Elm.Syntax.Range exposing (Range) import ParserFast -import ParserFast.Advanced -import ParserFast.Extra -import Set exposing (Set) -import Unicode - - -reservedList : Set String -reservedList = - [ "module" - , "exposing" - , "import" - , "as" - , "if" - , "then" - , "else" - , "let" - , "in" - , "case" - , "of" - , "port" - - --, "infixr" - --, "infixl" - , "type" - - --, "infix" Apparently this is not a reserved keyword - --, "alias" Apparently this is not a reserved keyword - , "where" - ] - |> Set.fromList + + +isNotReserved : String -> Bool +isNotReserved name = + case name of + "module" -> + False + + "exposing" -> + False + + "import" -> + False + + "as" -> + False + + "if" -> + False + + "then" -> + False + + "else" -> + False + + "let" -> + False + + "in" -> + False + + "case" -> + False + + "of" -> + False + + "port" -> + False + + --"infixr" + --"infixl" + "type" -> + False + + -- "infix" Apparently this is not a reserved keyword + -- "alias" Apparently this is not a reserved keyword + "where" -> + False + + _ -> + True inToken : ParserFast.Parser () @@ -61,139 +83,242 @@ inToken = ParserFast.keyword "in" () -escapedCharValue : ParserFast.Parser Char -escapedCharValue = - ParserFast.oneOf - [ ParserFast.symbol "'" '\'' - , ParserFast.symbol "\"" '"' - , ParserFast.symbol "n" '\n' - , ParserFast.symbol "t" '\t' - , -- Eventhough Elm-format will change \r to a unicode version. When you dont use elm-format, this will not happen. - ParserFast.symbol "r" '\u{000D}' - , ParserFast.symbol "\\" '\\' - , ParserFast.map2 - (\hex () -> - case String.toLower hex |> Hex.fromString of - Ok n -> - Char.fromCode n - - Err _ -> - '\u{0000}' - ) - (ParserFast.symbolFollowedBy "u{" - (ParserFast.variable - { inner = Char.isHexDigit - , reserved = Set.empty - , start = Char.isHexDigit - } +escapedCharValueMap : (Char -> res) -> ParserFast.Parser res +escapedCharValueMap charToRes = + ParserFast.oneOf7 + (ParserFast.symbol "'" (charToRes '\'')) + (ParserFast.symbol "\"" (charToRes '"')) + (ParserFast.symbol "n" (charToRes '\n')) + (ParserFast.symbol "t" (charToRes '\t')) + -- Eventhough Elm-format will change \r to a unicode version. When you dont use elm-format, this will not happen. + (ParserFast.symbol "r" (charToRes '\u{000D}')) + (ParserFast.symbol "\\" (charToRes '\\')) + (ParserFast.symbolFollowedBy "u{" + (ParserFast.ifFollowedByWhileMapWithoutLinebreak + (\hex -> + charToRes (Char.fromCode (hexStringToInt hex)) ) + Char.isHexDigit + Char.isHexDigit + |> ParserFast.followedBySymbol "}" ) - (ParserFast.symbol "}" ()) - ] + ) + + +hexStringToInt : String -> Int +hexStringToInt string = + String.foldr + (\c soFar -> + { exponent = soFar.exponent + 1 + , result = soFar.result + 16 ^ soFar.exponent * charToHex c + } + ) + { exponent = 0, result = 0 } + string + |> .result + + +charToHex : Char -> Int +charToHex c = + case c of + '0' -> + 0 + + '1' -> + 1 + + '2' -> + 2 + + '3' -> + 3 + + '4' -> + 4 + + '5' -> + 5 + + '6' -> + 6 + + '7' -> + 7 + + '8' -> + 8 + + '9' -> + 9 + + 'a' -> + 10 + + 'b' -> + 11 + + 'c' -> + 12 + + 'd' -> + 13 + + 'e' -> + 14 + + 'f' -> + 15 + 'A' -> + 10 -slashEscapedCharValue : ParserFast.Parser Char -slashEscapedCharValue = - ParserFast.symbolFollowedBy "\\" escapedCharValue + 'B' -> + 11 + 'C' -> + 12 -characterLiteral : ParserFast.Parser Char -characterLiteral = - ParserFast.map2 - (\res () -> res) - (ParserFast.symbolFollowedBy "'" - (ParserFast.oneOf2 - slashEscapedCharValue - ParserFast.Extra.anyChar + 'D' -> + 13 + + 'E' -> + 14 + + -- 'F' + _ -> + 15 + + +characterLiteralMapWithRange : (Range -> Char -> res) -> ParserFast.Parser res +characterLiteralMapWithRange rangeAndCharToRes = + ParserFast.symbolFollowedBy "'" + (ParserFast.oneOf2MapWithStartRowColumnAndEndRowColumn + (\startRow startColumn char endRow endColumn -> + rangeAndCharToRes + { start = { row = startRow, column = startColumn - 1 } + , end = { row = endRow, column = endColumn + 1 } + } + char ) + (ParserFast.symbolFollowedBy "\\" (escapedCharValueMap identity)) + (\startRow startColumn char endRow endColumn -> + rangeAndCharToRes + { start = { row = startRow, column = startColumn - 1 } + , end = { row = endRow, column = endColumn + 1 } + } + char + ) + ParserFast.anyChar + |> ParserFast.followedBySymbol "'" ) - (ParserFast.symbol "'" ()) -singleOrTripleQuotedStringLiteral : ParserFast.Parser String -singleOrTripleQuotedStringLiteral = +singleOrTripleQuotedStringLiteralMapWithRange : (Range -> String -> res) -> ParserFast.Parser res +singleOrTripleQuotedStringLiteralMapWithRange rangeAndStringToRes = ParserFast.symbolFollowedBy "\"" - (ParserFast.oneOf2 + (ParserFast.oneOf2MapWithStartRowColumnAndEndRowColumn + (\startRow startColumn string endRow endColumn -> + rangeAndStringToRes + { start = { row = startRow, column = startColumn - 1 } + , end = { row = endRow, column = endColumn } + } + string + ) (ParserFast.symbolFollowedBy "\"\"" - (ParserFast.Advanced.loop "" tripleQuotedStringLiteralStep) + tripleQuotedStringLiteralOfterTripleDoubleQuote ) - (ParserFast.Advanced.loop "" stringLiteralHelper) + (\startRow startColumn string endRow endColumn -> + rangeAndStringToRes + { start = { row = startRow, column = startColumn - 1 } + , end = { row = endRow, column = endColumn } + } + string + ) + singleQuotedStringLiteralAfterDoubleQuote ) -stringLiteralHelper : String -> ParserFast.Parser (ParserFast.Advanced.Step String String) -stringLiteralHelper stringSoFar = - ParserFast.oneOf - [ ParserFast.symbol "\"" (ParserFast.Advanced.Done stringSoFar) - , ParserFast.map - (\v -> - ParserFast.Advanced.Loop (stringSoFar ++ String.fromChar v ++ "") - ) - (ParserFast.symbolFollowedBy "\\" escapedCharValue) - , ParserFast.mapChompedString - (\value () -> ParserFast.Advanced.Loop (stringSoFar ++ value ++ "")) - chompWhileIsInsideString - ] - - -chompWhileIsInsideString : ParserFast.Parser () -chompWhileIsInsideString = - ParserFast.chompWhile (\c -> c /= '"' && c /= '\\') - - -tripleQuotedStringLiteralStep : String -> ParserFast.Parser (ParserFast.Advanced.Step String String) -tripleQuotedStringLiteralStep stringSoFar = - ParserFast.oneOf - [ ParserFast.symbol "\"\"\"" (ParserFast.Advanced.Done stringSoFar) - , ParserFast.symbol "\"" (ParserFast.Advanced.Loop (stringSoFar ++ "\"")) - , ParserFast.map - (\v -> - ParserFast.Advanced.Loop (stringSoFar ++ String.fromChar v ++ "") - ) - (ParserFast.symbolFollowedBy "\\" escapedCharValue) - , ParserFast.mapChompedString - (\value () -> ParserFast.Advanced.Loop (stringSoFar ++ value ++ "")) - chompWhileIsInsideString - ] +singleQuotedStringLiteralAfterDoubleQuote : ParserFast.Parser String +singleQuotedStringLiteralAfterDoubleQuote = + ParserFast.loopUntil (ParserFast.symbol "\"" ()) + (ParserFast.oneOf2 + (ParserFast.symbolFollowedBy "\\" (escapedCharValueMap String.fromChar)) + (ParserFast.whileWithoutLinebreak (\c -> c /= '"' && c /= '\\' && not (Char.Extra.isUtf16Surrogate c))) + ) + "" + (\extension soFar -> + soFar ++ extension ++ "" + ) + identity + + +tripleQuotedStringLiteralOfterTripleDoubleQuote : ParserFast.Parser String +tripleQuotedStringLiteralOfterTripleDoubleQuote = + ParserFast.loopUntil (ParserFast.symbol "\"\"\"" ()) + (ParserFast.oneOf3 + (ParserFast.symbol "\"" "\"") + (ParserFast.symbolFollowedBy "\\" (escapedCharValueMap String.fromChar)) + (ParserFast.while (\c -> c /= '"' && c /= '\\' && not (Char.Extra.isUtf16Surrogate c))) + ) + "" + (\extension soFar -> + soFar ++ extension ++ "" + ) + identity functionName : ParserFast.Parser String functionName = - ParserFast.variable - { inner = - \c -> - -- checking for these common ranges early is much faster - Char.Extra.isAlphaNumFast c || c == '_' || Unicode.isAlphaNum c - , reserved = reservedList - , start = - \c -> Char.isLower c || Unicode.isLower c - } - - -functionNameNotInfix : ParserFast.Parser String -functionNameNotInfix = - ParserFast.variable - { inner = - \c -> - -- checking for these common ranges early is much faster - Char.Extra.isAlphaNumFast c || c == '_' || Unicode.isAlphaNum c - , reserved = Set.insert "infix" reservedList - , start = - \c -> Char.isLower c || Unicode.isLower c - } + ParserFast.ifFollowedByWhileValidateWithoutLinebreak + Char.Extra.unicodeIsLowerFast + Char.Extra.unicodeIsAlphaNumOrUnderscoreFast + isNotReserved + + +functionNameNode : ParserFast.Parser (Node String) +functionNameNode = + ParserFast.ifFollowedByWhileValidateMapWithRangeWithoutLinebreak Node + Char.Extra.unicodeIsLowerFast + Char.Extra.unicodeIsAlphaNumOrUnderscoreFast + isNotReserved + + +functionNameMapWithRange : (Range -> String -> res) -> ParserFast.Parser res +functionNameMapWithRange rangeAndNameToResult = + ParserFast.ifFollowedByWhileValidateMapWithRangeWithoutLinebreak + rangeAndNameToResult + Char.Extra.unicodeIsLowerFast + Char.Extra.unicodeIsAlphaNumOrUnderscoreFast + isNotReserved + + +functionNameNotInfixNode : ParserFast.Parser (Node String) +functionNameNotInfixNode = + ParserFast.ifFollowedByWhileValidateMapWithRangeWithoutLinebreak Node + Char.Extra.unicodeIsLowerFast + Char.Extra.unicodeIsAlphaNumOrUnderscoreFast + (\name -> name /= "infix" && isNotReserved name) typeName : ParserFast.Parser String typeName = - ParserFast.variable - { inner = - \c -> - -- checking for these common ranges early is much faster - Char.Extra.isAlphaNumFast c || c == '_' || Unicode.isAlphaNum c - , reserved = Set.empty - , start = - \c -> Char.isUpper c || Unicode.isUpper c - } + ParserFast.ifFollowedByWhileWithoutLinebreak + Char.Extra.unicodeIsUpperFast + Char.Extra.unicodeIsAlphaNumOrUnderscoreFast + + +typeNameMapWithRange : (Range -> String -> res) -> ParserFast.Parser res +typeNameMapWithRange rangeAndNameToRes = + ParserFast.ifFollowedByWhileMapWithRangeWithoutLinebreak rangeAndNameToRes + Char.Extra.unicodeIsUpperFast + Char.Extra.unicodeIsAlphaNumOrUnderscoreFast + + +typeNameNode : ParserFast.Parser (Node String) +typeNameNode = + ParserFast.ifFollowedByWhileMapWithRangeWithoutLinebreak Node + Char.Extra.unicodeIsUpperFast + Char.Extra.unicodeIsAlphaNumOrUnderscoreFast allowedOperatorTokens : List String @@ -232,30 +357,6 @@ prefixOperatorToken = |> ParserFast.oneOf -minusFollowedBySingleWhitespace : ParserFast.Parser res -> ParserFast.Parser res -minusFollowedBySingleWhitespace next = - ParserFast.oneOf - [ ParserFast.symbolFollowedBy "- " next - , ParserFast.symbolFollowedBy "-\n" next - , ParserFast.symbolFollowedBy "-\u{000D}" next - ] - - -squareEnd : ParserFast.Parser () -squareEnd = - ParserFast.symbol "]" () - - -curlyEnd : ParserFast.Parser () -curlyEnd = - ParserFast.symbol "}" () - - -arrowRight : ParserFast.Parser () -arrowRight = - ParserFast.symbol "->" () - - equal : ParserFast.Parser () equal = ParserFast.symbol "=" () diff --git a/src/Elm/Parser/TypeAnnotation.elm b/src/Elm/Parser/TypeAnnotation.elm index c3fec280..622492bc 100644 --- a/src/Elm/Parser/TypeAnnotation.elm +++ b/src/Elm/Parser/TypeAnnotation.elm @@ -3,8 +3,8 @@ module Elm.Parser.TypeAnnotation exposing (typeAnnotation, typeAnnotationNoFnExc import Elm.Parser.Layout as Layout import Elm.Parser.Node as Node import Elm.Parser.Tokens as Tokens +import Elm.Syntax.ModuleName exposing (ModuleName) import Elm.Syntax.Node as Node exposing (Node(..)) -import Elm.Syntax.Range exposing (Range) import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (RecordDefinition, RecordField, TypeAnnotation) import ParserFast exposing (Parser) import ParserWithComments exposing (WithComments) @@ -25,45 +25,39 @@ typeAnnotation = } ) (ParserFast.lazy (\() -> typeAnnotationNoFnIncludingTypedWithArguments)) - (ParserFast.orSucceed - (ParserFast.map3 - (\commentsBeforeArrow commentsAfterArrow typeAnnotationResult -> - Just - { comments = - commentsBeforeArrow - |> Rope.prependTo commentsAfterArrow - |> Rope.prependTo typeAnnotationResult.comments - , syntax = typeAnnotationResult.syntax - } - ) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy "->" - |> ParserFast.backtrackable - ) - Layout.maybeLayout - (ParserFast.lazy (\() -> typeAnnotation)) + (ParserFast.map3OrSucceed + (\commentsBeforeArrow commentsAfterArrow typeAnnotationResult -> + Just + { comments = + commentsBeforeArrow + |> Rope.prependTo commentsAfterArrow + |> Rope.prependTo typeAnnotationResult.comments + , syntax = typeAnnotationResult.syntax + } ) + (Layout.maybeLayoutBacktrackable |> ParserFast.followedBySymbol "->") + Layout.maybeLayout + (ParserFast.lazy (\() -> typeAnnotation)) Nothing ) typeAnnotationNoFnExcludingTypedWithArguments : Parser (WithComments (Node TypeAnnotation)) typeAnnotationNoFnExcludingTypedWithArguments = - ParserFast.oneOf - [ parensTypeAnnotation - , typedTypeAnnotationWithoutArguments - , genericTypeAnnotation - , recordTypeAnnotation - ] + ParserFast.oneOf4 + parensTypeAnnotation + typedTypeAnnotationWithoutArguments + genericTypeAnnotation + recordTypeAnnotation typeAnnotationNoFnIncludingTypedWithArguments : Parser (WithComments (Node TypeAnnotation)) typeAnnotationNoFnIncludingTypedWithArguments = - ParserFast.oneOf - [ parensTypeAnnotation - , typedTypeAnnotationWithArguments - , genericTypeAnnotation - , recordTypeAnnotation - ] + ParserFast.oneOf4 + parensTypeAnnotation + typedTypeAnnotationWithArguments + genericTypeAnnotation + recordTypeAnnotation parensTypeAnnotation : Parser (WithComments (Node TypeAnnotation)) @@ -117,38 +111,36 @@ parensTypeAnnotation = genericTypeAnnotation : Parser (WithComments (Node TypeAnnotation)) genericTypeAnnotation = - Tokens.functionName - |> ParserFast.mapWithStartAndEndPosition - (\start var end -> - { comments = Rope.empty - , syntax = - Node { start = start, end = end } - (TypeAnnotation.GenericType var) - } - ) + Tokens.functionNameMapWithRange + (\range var -> + { comments = Rope.empty + , syntax = + Node range (TypeAnnotation.GenericType var) + } + ) recordTypeAnnotation : Parser (WithComments (Node TypeAnnotation)) recordTypeAnnotation = - ParserFast.map2 - (\commentsBefore afterCurly -> + ParserFast.map2WithRange + (\range commentsBefore afterCurly -> case afterCurly of Nothing -> { comments = commentsBefore - , syntax = typeAnnotationRecordEmpty + , syntax = Node range typeAnnotationRecordEmpty } Just afterCurlyResult -> { comments = commentsBefore |> Rope.prependTo afterCurlyResult.comments - , syntax = afterCurlyResult.syntax + , syntax = Node range afterCurlyResult.syntax } ) (ParserFast.symbolFollowedBy "{" Layout.maybeLayout) (ParserFast.oneOf2 - (ParserFast.map4 - (\firstNameNode commentsAfterFirstName afterFirstName () -> + (ParserFast.map3 + (\firstNameNode commentsAfterFirstName afterFirstName -> Just { comments = commentsAfterFirstName @@ -162,17 +154,36 @@ recordTypeAnnotation = TypeAnnotation.Record (Node.combine Tuple.pair firstNameNode fieldsAfterName.firstFieldValue :: fieldsAfterName.tailFields) } ) - (Node.parserCore Tokens.functionName) + Tokens.functionNameNode Layout.maybeLayout (ParserFast.oneOf2 - (ParserFast.map - (\extension -> - { comments = extension.comments - , syntax = RecordExtensionExpressionAfterName extension.syntax - } - ) - (ParserFast.symbolFollowedBy "|" - (Node.parser recordFieldsTypeAnnotation) + (ParserFast.symbolFollowedBy "|" + (ParserFast.map3WithRange + (\range commentsBefore head tail -> + { comments = + commentsBefore + |> Rope.prependTo head.comments + |> Rope.prependTo tail.comments + , syntax = + RecordExtensionExpressionAfterName + (Node range (head.syntax :: tail.syntax)) + } + ) + Layout.maybeLayout + recordFieldDefinition + (ParserWithComments.many + (ParserFast.symbolFollowedBy "," + (ParserFast.map2 + (\commentsBefore field -> + { comments = commentsBefore |> Rope.prependTo field.comments + , syntax = field.syntax + } + ) + Layout.maybeLayout + recordFieldDefinition + ) + ) + ) ) ) (ParserFast.map4 @@ -198,11 +209,10 @@ recordTypeAnnotation = ) ) ) - Tokens.curlyEnd + |> ParserFast.followedBySymbol "}" ) (ParserFast.symbol "}" Nothing) ) - |> Node.parser typeAnnotationRecordEmpty : TypeAnnotation @@ -217,34 +227,48 @@ type RecordFieldsOrExtensionAfterName recordFieldsTypeAnnotation : Parser (WithComments TypeAnnotation.RecordDefinition) recordFieldsTypeAnnotation = - ParserWithComments.sepBy1 "," - (ParserFast.map2 - (\commentsBefore fields -> - { comments = commentsBefore |> Rope.prependTo fields.comments - , syntax = fields.syntax - } + ParserFast.map3 + (\commentsBefore head tail -> + { comments = + commentsBefore + |> Rope.prependTo head.comments + |> Rope.prependTo tail.comments + , syntax = head.syntax :: tail.syntax + } + ) + Layout.maybeLayout + recordFieldDefinition + (ParserWithComments.many + (ParserFast.symbolFollowedBy "," + (ParserFast.map2 + (\commentsBefore field -> + { comments = commentsBefore |> Rope.prependTo field.comments + , syntax = field.syntax + } + ) + Layout.maybeLayout + recordFieldDefinition + ) ) - Layout.maybeLayout - (Node.parser recordFieldDefinition) ) -recordFieldDefinition : Parser (WithComments TypeAnnotation.RecordField) +recordFieldDefinition : Parser (WithComments (Node TypeAnnotation.RecordField)) recordFieldDefinition = - ParserFast.map6 - (\commentsBeforeFunctionName name commentsAfterFunctionName commentsAfterColon value commentsAfterValue -> + ParserFast.map6WithRange + (\range commentsBeforeFunctionName name commentsAfterFunctionName commentsAfterColon value commentsAfterValue -> { comments = commentsBeforeFunctionName |> Rope.prependTo commentsAfterFunctionName |> Rope.prependTo commentsAfterColon |> Rope.prependTo value.comments |> Rope.prependTo commentsAfterValue - , syntax = ( name, value.syntax ) + , syntax = Node range ( name, value.syntax ) } ) Layout.maybeLayout - (Node.parserCore Tokens.functionName) - (Layout.maybeLayoutUntilIgnored ParserFast.symbolFollowedBy ":") + Tokens.functionNameNode + (Layout.maybeLayout |> ParserFast.followedBySymbol ":") Layout.maybeLayout typeAnnotation -- This extra whitespace is just included for compatibility with earlier version @@ -254,12 +278,17 @@ recordFieldDefinition = typedTypeAnnotationWithoutArguments : Parser (WithComments (Node TypeAnnotation)) typedTypeAnnotationWithoutArguments = - ParserFast.mapWithStartAndEndPosition - (\nameStart name nameEnd -> + ParserFast.map2WithRange + (\range startName afterStartName -> let - range : Range - range = - { start = nameStart, end = nameEnd } + name : ( ModuleName, String ) + name = + case afterStartName of + Nothing -> + ( [], startName ) + + Just ( qualificationAfterStartName, unqualified ) -> + ( startName :: qualificationAfterStartName, unqualified ) in { comments = Rope.empty , syntax = @@ -267,63 +296,51 @@ typedTypeAnnotationWithoutArguments = (TypeAnnotation.Typed (Node range name) []) } ) - (ParserFast.map2 - (\startName afterStartName -> - case afterStartName of - Nothing -> - ( [], startName ) - - Just ( qualificationAfterStartName, unqualified ) -> - ( startName :: qualificationAfterStartName, unqualified ) - ) - Tokens.typeName - maybeDotTypeNamesTuple - ) + Tokens.typeName + maybeDotTypeNamesTuple maybeDotTypeNamesTuple : ParserFast.Parser (Maybe ( List String, String )) maybeDotTypeNamesTuple = - ParserFast.orSucceed - (ParserFast.map2 - (\firstName afterFirstName -> - case afterFirstName of - Nothing -> - Just ( [], firstName ) - - Just ( qualificationAfter, unqualified ) -> - Just ( firstName :: qualificationAfter, unqualified ) - ) - (ParserFast.symbolFollowedBy "." Tokens.typeName) - (ParserFast.lazy (\() -> maybeDotTypeNamesTuple)) + ParserFast.map2OrSucceed + (\firstName afterFirstName -> + case afterFirstName of + Nothing -> + Just ( [], firstName ) + + Just ( qualificationAfter, unqualified ) -> + Just ( firstName :: qualificationAfter, unqualified ) ) + (ParserFast.symbolFollowedBy "." Tokens.typeName) + (ParserFast.lazy (\() -> maybeDotTypeNamesTuple)) Nothing typedTypeAnnotationWithArguments : Parser (WithComments (Node TypeAnnotation)) typedTypeAnnotationWithArguments = - ParserFast.map2 - (\nameNode args -> + ParserFast.map2WithRange + (\range nameNode args -> { comments = args.comments - , syntax = TypeAnnotation.Typed nameNode args.syntax + , syntax = + Node range (TypeAnnotation.Typed nameNode args.syntax) } ) - (ParserFast.mapWithStartAndEndPosition - (\nameStart name nameEnd -> - Node { start = nameStart, end = nameEnd } - name - ) - (ParserFast.map2 - (\startName afterStartName -> - case afterStartName of - Nothing -> - ( [], startName ) - - Just ( qualificationAfterStartName, unqualified ) -> - ( startName :: qualificationAfterStartName, unqualified ) - ) - Tokens.typeName - maybeDotTypeNamesTuple + (ParserFast.map2WithRange + (\range startName afterStartName -> + let + name : ( ModuleName, String ) + name = + case afterStartName of + Nothing -> + ( [], startName ) + + Just ( qualificationAfterStartName, unqualified ) -> + ( startName :: qualificationAfterStartName, unqualified ) + in + Node range name ) + Tokens.typeName + maybeDotTypeNamesTuple ) (ParserWithComments.many (ParserFast.map2 @@ -332,8 +349,7 @@ typedTypeAnnotationWithArguments = , syntax = typeAnnotationResult.syntax } ) - (Layout.maybeLayout |> ParserFast.backtrackable) + Layout.maybeLayoutBacktrackable typeAnnotationNoFnExcludingTypedWithArguments ) ) - |> Node.parser diff --git a/src/ParserFast.elm b/src/ParserFast.elm index 3c4514ca..98265d0d 100644 --- a/src/ParserFast.elm +++ b/src/ParserFast.elm @@ -1,49 +1,91 @@ module ParserFast exposing ( Parser, run - , int, number, symbol, symbolFollowedBy, keyword, keywordFollowedBy, variable, end - , succeed, problem, lazy, map, map2, map3, map4, map5, map6, map7, map8, map9, ignore, andThen - , orSucceed, orSucceedLazy, oneOf2, oneOf, backtrackable - , nestableMultiComment - , getChompedString, chompIf, chompIfFollowedBy, chompWhile, mapChompedString - , withIndentSetToColumn, withIndent, columnIndentAndThen - , mapWithStartPosition, mapWithEndPosition, mapWithStartAndEndPosition, columnAndThen, offsetSourceAndThen + , int, intOrHexMapWithRange, floatOrIntOrHexMapWithRange, symbol, symbolWithEndLocation, symbolWithRange, symbolFollowedBy, symbolBacktrackableFollowedBy, followedBySymbol, keyword, keywordFollowedBy, while, whileWithoutLinebreak, whileMap, ifFollowedByWhileWithoutLinebreak, ifFollowedByWhileMapWithoutLinebreak, ifFollowedByWhileMapWithRangeWithoutLinebreak, ifFollowedByWhileValidateWithoutLinebreak, ifFollowedByWhileValidateMapWithRangeWithoutLinebreak, anyChar, end + , succeed, problem, lazy, map, map2, map2WithStartLocation, map2WithRange, map3, map3WithStartLocation, map3WithRange, map4, map4WithRange, map5, map5WithStartLocation, map5WithRange, map6, map6WithStartLocation, map6WithRange, map7WithRange, map8WithStartLocation, map9WithRange, validate + , orSucceed, mapOrSucceed, map2OrSucceed, map3OrSucceed, map4OrSucceed, oneOf2, oneOf2Map, oneOf2MapWithStartRowColumnAndEndRowColumn, oneOf2OrSucceed, oneOf3, oneOf4, oneOf5, oneOf7, oneOf10, oneOf14, oneOf + , loopWhileSucceeds, loopUntil + , chompWhileWhitespaceFollowedBy, followedByChompWhileWhitespace, nestableMultiCommentMapWithRange + , withIndentSetToColumn, withIndentSetToColumnMinus, columnIndentAndThen, validateEndColumnIndentation, validateEndColumnIndentationBacktrackable + , mapWithRange, columnAndThen, offsetSourceAndThen + , withIndent ) {-| @docs Parser, run -@docs int, number, symbol, symbolFollowedBy, keyword, keywordFollowedBy, variable, end +@docs int, intOrHexMapWithRange, floatOrIntOrHexMapWithRange, symbol, symbolWithEndLocation, symbolWithRange, symbolFollowedBy, symbolBacktrackableFollowedBy, followedBySymbol, keyword, keywordFollowedBy, while, whileWithoutLinebreak, whileMap, ifFollowedByWhileWithoutLinebreak, ifFollowedByWhileMapWithoutLinebreak, ifFollowedByWhileMapWithRangeWithoutLinebreak, ifFollowedByWhileValidateWithoutLinebreak, ifFollowedByWhileValidateMapWithRangeWithoutLinebreak, anyChar, end # Flow -@docs succeed, problem, lazy, map, map2, map3, map4, map5, map6, map7, map8, map9, ignore, andThen +@docs succeed, problem, lazy, map, map2, map2WithStartLocation, map2WithRange, map3, map3WithStartLocation, map3WithRange, map4, map4WithRange, map5, map5WithStartLocation, map5WithRange, map6, map6WithStartLocation, map6WithRange, map7WithRange, map8WithStartLocation, map9WithRange, validate -@docs orSucceed, orSucceedLazy, oneOf2, oneOf, backtrackable +Choice: parsing JSON for example, the values can be strings, floats, booleans, +arrays, objects, or null. You need a way to pick one of them! Here is a +sample of what that code might look like: + + type Json + = Number Float + | Boolean Bool + | Null + + json : Parser Json + json = + oneOf4 + (ParserFast.map Number float) + (ParserFast.keyword "true" (Boolean True)) + (ParserFast.keyword "False" (Boolean False)) + (ParserFast.keyword "null" Null) + +This parser will keep trying down the list of parsers until one of them starts chomping +characters. Once a path is chosen, it does not come back and try the others. + +**Note:** I highly recommend reading [this document][semantics] to learn how +`oneOf` and `backtrackable` interact. It is subtle and important! + +[semantics]: https://github.com/elm/parser/blob/master/semantics.md + +@docs orSucceed, mapOrSucceed, map2OrSucceed, map3OrSucceed, map4OrSucceed, oneOf2, oneOf2Map, oneOf2MapWithStartRowColumnAndEndRowColumn, oneOf2OrSucceed, oneOf3, oneOf4, oneOf5, oneOf7, oneOf10, oneOf14, oneOf + +@docs loopWhileSucceeds, loopUntil # Whitespace -@docs nestableMultiComment +@docs chompWhileWhitespaceFollowedBy, followedByChompWhileWhitespace, nestableMultiCommentMapWithRange -# Chompers +# Indentation, Locations and source -@docs getChompedString, chompIf, chompIfFollowedBy, chompWhile, mapChompedString +@docs withIndentSetToColumn, withIndentSetToColumnMinus, columnIndentAndThen, validateEndColumnIndentation, validateEndColumnIndentationBacktrackable +@docs mapWithRange, columnAndThen, offsetSourceAndThen -# Indentation, Positions and source +# test-only -@docs withIndentSetToColumn, withIndent, columnIndentAndThen -@docs mapWithStartPosition, mapWithEndPosition, mapWithStartAndEndPosition, columnAndThen, offsetSourceAndThen +@docs withIndent -} -import Elm.Syntax.Range exposing (Location) +import Char +import Char.Extra +import Elm.Syntax.Range exposing (Location, Range) import Parser -import ParserFast.Advanced as A -import Set +import Parser.Advanced exposing ((|=)) + + +type Problem + = ExpectingNumber Int Int () + | ExpectingSymbol Int Int String + | ExpectingAnyChar Int Int () + | ExpectingKeyword Int Int String + | ExpectingEnd Int Int () + | ExpectingCharSatisfyingPredicate Int Int () + | ExpectingStringSatisfyingPredicate Int Int () + | ExpectingCustom Int Int String + | ExpectingNonEmptyOneOf Int Int () + | ExpectingOneOf Problem Problem (List Problem) {-| A `Parser` helps turn a `String` into nicely structured data. For example, @@ -56,20 +98,34 @@ The cool thing is that you can combine `Parser` values to handle much more complex scenarios. -} -type alias Parser a = - A.Parser Parser.Problem a +type Parser a + = Parser (State -> PStep a) + + +type PStep value + = Good Bool value State + | Bad Bool Problem () + + +type alias State = + { src : String + , offset : Int + , indent : Int + , row : Int + , col : Int + } {-| Try a parser. Here are some examples using the [`keyword`](#keyword) parser: - run (keyword "true") "true" --> Ok () + run (keyword "true" ()) "true" --> Ok () - run (keyword "true") "True" --> Err .. + run (keyword "true" ()) "True" --> Err .. - run (keyword "true") "false" --> Err ... + run (keyword "true" ()) "false" --> Err ... - run (keyword "true") "true!" --> Ok () + run (keyword "true" ()) "true!" --> Ok () Notice the last case! A `Parser` will chomp as much as possible and not worry about the rest. Use the [`end`](#end) parser to ensure you made it to the end @@ -80,65 +136,74 @@ to avoid breaking changes -} run : Parser a -> String -> Result (List Parser.DeadEnd) a -run parser source = - case A.run parser source of - Ok a -> - Ok a +run (Parser parse) src = + case parse { src = src, offset = 0, indent = 1, row = 1, col = 1 } of + Good _ value _ -> + Ok value - Err problems -> - Err problems + Bad _ deadEnds () -> + Err (ropeFilledToList deadEnds []) -{-| A parser that succeeds without chomping any characters. +ropeFilledToList : Problem -> List Parser.DeadEnd -> List Parser.DeadEnd +ropeFilledToList problemToConvert soFar = + case problemToConvert of + ExpectingOneOf firstTry secondTry thirdTryUp -> + List.foldr ropeFilledToList soFar thirdTryUp + |> ropeFilledToList secondTry + |> ropeFilledToList firstTry - run (succeed 90210) "mississippi" == Ok 90210 + ExpectingNumber row col () -> + { row = row, col = col, problem = Parser.ExpectingNumber } :: soFar - run (succeed 3.141) "mississippi" == Ok 3.141 + ExpectingSymbol row col symbolString -> + { row = row, col = col, problem = Parser.ExpectingSymbol symbolString } :: soFar - run (succeed ()) "mississippi" == Ok () + ExpectingAnyChar row col () -> + { row = row, col = col, problem = Parser.Problem "expecting any char" } :: soFar - run (succeed Nothing) "mississippi" == Ok Nothing + ExpectingKeyword row col keywordString -> + { row = row, col = col, problem = Parser.ExpectingKeyword keywordString } :: soFar -Seems weird on its own, but it is very useful in combination with other -functions. + ExpectingEnd row col () -> + { row = row, col = col, problem = Parser.ExpectingEnd } :: soFar --} -succeed : a -> Parser a -succeed = - A.succeed + ExpectingCharSatisfyingPredicate row col () -> + { row = row, col = col, problem = Parser.UnexpectedChar } :: soFar + + ExpectingStringSatisfyingPredicate row col () -> + { row = row, col = col, problem = Parser.Problem "expected string to pass validation" } :: soFar + ExpectingCustom row col customMessage -> + { row = row, col = col, problem = Parser.Problem customMessage } :: soFar -{-| **Skip** values in a parser pipeline. For example, maybe we want to parse -some JavaScript variables: + ExpectingNonEmptyOneOf row col () -> + { row = row, col = col, problem = Parser.Problem "expecting oneOf list to have at least one member" } :: soFar - var : Parser String - var = - getChompedString <| - succeed () - |> ParserFast.ignore chompIf isStartChar - |> ParserFast.ignore chompWhile isInnerChar - isStartChar : Char -> Bool - isStartChar char = - Char.isAlpha char || char == '_' || char == '$' +{-| A parser that succeeds without chomping any characters. - isInnerChar : Char -> Bool - isInnerChar char = - isStartChar char || Char.isDigit char + run (succeed 90210) "mississippi" == Ok 90210 -`chompIf isStartChar` can chomp one character and produce a `()` value. -`chompWhile isInnerChar` can chomp zero or more characters and produce a `()` -value. The `ignore` operators are saying to still chomp all the characters, but -skip the two `()` values that get produced. No one cares about them. + run (succeed 3.141) "mississippi" == Ok 3.141 + + run (succeed ()) "mississippi" == Ok () + + run (succeed Nothing) "mississippi" == Ok Nothing + +Sometimes useful in combination with -andThen helpers, +though there are usually alternatives like the -orSucceed versions to consider first. -} -ignore : Parser ignore -> Parser keep -> Parser keep -ignore ignored kept = - A.ignore kept ignored +succeed : a -> Parser a +succeed a = + Parser (\s -> Good False a s) + +{-| Helper to delay computation, +mostly to refer to a recursive parser. -{-| Helper to define recursive parsers. Say we want a parser for simple -boolean expressions: +Say we want a parser for simple boolean expressions: true @@ -158,93 +223,127 @@ That means we will want to define our parser in terms of itself: boolean : Parser Boolean boolean = - oneOf - [ succeed MyTrue - |> ParserFast.ignore keyword "true" - , succeed MyFalse - |> ParserFast.ignore keyword "false" - , succeed MyOr - |> ParserFast.ignore symbol "(" - |> ParserFast.ignore spaces - |= lazy (\_ -> boolean) - |> ParserFast.ignore spaces - |> ParserFast.ignore symbol "||" - |> ParserFast.ignore spaces - |= lazy (\_ -> boolean) - |> ParserFast.ignore spaces - |> ParserFast.ignore symbol ")" - ] + ParserFast.oneOf3 + (ParserFast.keyword "true" MyTrue) + (ParserFast.keyword "false" MyFalse) + (ParserFast.symbolFollowedBy "(" + (ParserFast.map2 MyOr + (ParserFast.chompWhileWhitespaceFollowedBy + (ParserFast.lazy (\_ -> boolean)) + |> ParserFast.followedByChompWhileWhitespace + |> ParserFast.followedBySymbol "||" + |> ParserFast.followedByChompWhileWhitespace + ) + (ParserFast.lazy (\_ -> boolean) + |> ParserFast.followedByChompWhileWhitespace + ) + ) + |> ParserFast.followedBySymbol ")" + ) **Notice that `boolean` uses `boolean` in its definition!** In Elm, you can only define a value in terms of itself it is behind a function call. So -`lazy` helps us define these self-referential parsers. (`andThen` can be used -for this as well!) +`lazy` helps us define these self-referential parsers. + +If the recursion is linear, first consider the loop- helpers to avoid stack overflows -} lazy : (() -> Parser a) -> Parser a -lazy = - A.lazy - - -{-| Parse one thing `andThen` parse another thing. This is useful when you want -to check on what you just parsed. For example, maybe you want U.S. zip codes -and `int` is not suitable because it does not allow leading zeros. You could -say: - - zipCode : Parser String - zipCode = - getChompedString (chompWhile Char.isDigit) - |> andThen checkZipCode - - checkZipCode : String -> Parser String - checkZipCode code = - if String.length code == 5 then - succeed code +lazy thunk = + Parser + (\s -> + let + (Parser parse) = + thunk () + in + parse s + ) + + +validate : (a -> Bool) -> String -> Parser a -> Parser a +validate isOkay problemOnNotOkay (Parser parseA) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + (Good committed a s1) as good -> + if isOkay a then + good - else - problem "a U.S. zip code has exactly 5 digits" - -First we chomp digits `andThen` we check if it is a valid U.S. zip code. We -`succeed` if it has exactly five digits and report a `problem` if not. - -Check out [`examples/DoubleQuoteString.elm`](https://github.com/elm/parser/blob/master/examples/DoubleQuoteString.elm) -for another example, this time using `andThen` to verify unicode code points. - -**Note:** If you are using `andThen` recursively and blowing the stack, check -out the [`ParserFast.Advanced.loop`](ParserFast-Advanced#loop) function to limit stack usage. - --} -andThen : (a -> Parser b) -> Parser a -> Parser b -andThen = - A.andThen + else + Bad committed (ExpectingCustom s1.row s1.col problemOnNotOkay) () + ) columnAndThen : (Int -> Parser a) -> Parser a -columnAndThen = - A.columnAndThen +columnAndThen callback = + Parser + (\s -> + let + (Parser parse) = + callback s.col + in + parse s + ) {-| Can be used to verify the current indentation like this: checkIndent : Parser () checkIndent = - columnIndentAndThen (\indent column -> checkIndentHelp (indent <= column)) - - checkIndentHelp : Bool -> Parser () - checkIndentHelp isIndented = - if isIndented then - succeed () - - else - problem "expecting more spaces" + columnIndentAndThen (\indent column -> column > indent) + "expecting more spaces" So the `checkIndent` parser only succeeds when you are "deeper" than the -current indent level. You could use this to parse Elm-style `let` expressions. +current indent level. You could use this to parse elm-style `let` expressions. -} columnIndentAndThen : (Int -> Int -> Parser b) -> Parser b -columnIndentAndThen = - A.columnIndentAndThen +columnIndentAndThen callback = + Parser + (\s -> + let + (Parser parse) = + callback s.col s.indent + in + parse s + ) + + +validateEndColumnIndentation : (Int -> Int -> Bool) -> String -> Parser a -> Parser a +validateEndColumnIndentation isOkay problemOnIsNotOkay (Parser parse) = + Parser + (\s0 -> + case parse s0 of + (Good committed _ s1) as good -> + if isOkay s1.col s1.indent then + good + + else + Bad committed (ExpectingCustom s1.row s1.col problemOnIsNotOkay) () + + bad -> + bad + ) + + +validateEndColumnIndentationBacktrackable : (Int -> Int -> Bool) -> String -> Parser a -> Parser a +validateEndColumnIndentationBacktrackable isOkay problemOnIsNotOkay (Parser parse) = + Parser + (\s0 -> + case parse s0 of + Good _ res s1 -> + if isOkay s1.col s1.indent then + Good False res s1 + + else + Bad False (ExpectingCustom s1.row s1.col problemOnIsNotOkay) () + + Bad _ x () -> + Bad False x () + ) {-| Editors think of code as a grid, but behind the scenes it is just a flat @@ -258,208 +357,1443 @@ moves by those rules. -} offsetSourceAndThen : (Int -> String -> Parser a) -> Parser a -offsetSourceAndThen = - A.offsetSourceAndThen +offsetSourceAndThen callback = + Parser + (\s -> + let + (Parser parse) = + callback s.offset s.src + in + parse s + ) {-| Parse 2 parser in sequence and combine their results -} map2 : (a -> b -> value) -> Parser a -> Parser b -> Parser value -map2 = - A.map2 +map2 func (Parser parseA) (Parser parseB) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + Good (c1 || c2) (func a b) s2 + ) + + +map2WithStartLocation : (Location -> a -> b -> value) -> Parser a -> Parser b -> Parser value +map2WithStartLocation func (Parser parseA) (Parser parseB) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + Good (c1 || c2) (func { row = s0.row, column = s0.col } a b) s2 + ) + + +map2WithRange : (Range -> a -> b -> value) -> Parser a -> Parser b -> Parser value +map2WithRange func (Parser parseA) (Parser parseB) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + Good (c1 || c2) (func { start = { row = s0.row, column = s0.col }, end = { row = s2.row, column = s2.col } } a b) s2 + ) map3 : (a -> b -> c -> value) -> Parser a -> Parser b -> Parser c -> Parser value -map3 = - A.map3 +map3 func (Parser parseA) (Parser parseB) (Parser parseC) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + Good (c1 || c2 || c3) (func a b c) s3 + ) + + +map3WithRange : (Range -> a -> b -> c -> value) -> Parser a -> Parser b -> Parser c -> Parser value +map3WithRange func (Parser parseA) (Parser parseB) (Parser parseC) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + Good (c1 || c2 || c3) (func { start = { row = s0.row, column = s0.col }, end = { row = s3.row, column = s3.col } } a b c) s3 + ) map4 : (a -> b -> c -> d -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser value -map4 = - A.map4 +map4 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + Good (c1 || c2 || c3 || c4) (func a b c d) s4 + ) + + +map4WithRange : (Range -> a -> b -> c -> d -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser value +map4WithRange func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + Good (c1 || c2 || c3 || c4) (func { start = { row = s0.row, column = s0.col }, end = { row = s4.row, column = s4.col } } a b c d) s4 + ) + + +map3WithStartLocation : (Location -> a -> b -> c -> value) -> Parser a -> Parser b -> Parser c -> Parser value +map3WithStartLocation func (Parser parseA) (Parser parseB) (Parser parseC) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + Good (c1 || c2 || c3) (func { row = s0.row, column = s0.col } a b c) s3 + ) map5 : (a -> b -> c -> d -> e -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser value -map5 = - A.map5 +map5 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + Good (c1 || c2 || c3 || c4 || c5) (func a b c d e) s5 + ) + + +map5WithStartLocation : (Location -> a -> b -> c -> d -> e -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser value +map5WithStartLocation func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + Good (c1 || c2 || c3 || c4 || c5) (func { row = s0.row, column = s0.col } a b c d e) s5 + ) + + +map5WithRange : (Range -> a -> b -> c -> d -> e -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser value +map5WithRange func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + Good (c1 || c2 || c3 || c4 || c5) (func { start = { row = s0.row, column = s0.col }, end = { row = s5.row, column = s5.col } } a b c d e) s5 + ) map6 : (a -> b -> c -> d -> e -> f -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser value -map6 = - A.map6 +map6 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + case parseF s5 of + Bad c6 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6) x () + + Good c6 f s6 -> + Good (c1 || c2 || c3 || c4 || c5 || c6) (func a b c d e f) s6 + ) + + +map6WithStartLocation : (Location -> a -> b -> c -> d -> e -> f -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser value +map6WithStartLocation func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + case parseF s5 of + Bad c6 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6) x () + + Good c6 f s6 -> + Good (c1 || c2 || c3 || c4 || c5 || c6) (func { row = s0.row, column = s0.col } a b c d e f) s6 + ) + + +map6WithRange : (Range -> a -> b -> c -> d -> e -> f -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser value +map6WithRange func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + case parseF s5 of + Bad c6 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6) x () + + Good c6 f s6 -> + Good (c1 || c2 || c3 || c4 || c5 || c6) (func { start = { row = s0.row, column = s0.col }, end = { row = s6.row, column = s6.col } } a b c d e f) s6 + ) + + +map7WithRange : (Range -> a -> b -> c -> d -> e -> f -> g -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser g -> Parser value +map7WithRange func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) (Parser parseG) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + case parseF s5 of + Bad c6 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6) x () + + Good c6 f s6 -> + case parseG s6 of + Bad c7 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6 || c7) x () + + Good c7 g s7 -> + Good (c1 || c2 || c3 || c4 || c5 || c6 || c7) (func { start = { row = s0.row, column = s0.col }, end = { row = s7.row, column = s7.col } } a b c d e f g) s7 + ) + + +map8WithStartLocation : (Location -> a -> b -> c -> d -> e -> f -> g -> h -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser g -> Parser h -> Parser value +map8WithStartLocation func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) (Parser parseG) (Parser parseH) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + case parseF s5 of + Bad c6 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6) x () + + Good c6 f s6 -> + case parseG s6 of + Bad c7 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6 || c7) x () + + Good c7 g s7 -> + case parseH s7 of + Bad c8 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6 || c7 || c8) x () + + Good c8 h s8 -> + Good (c1 || c2 || c3 || c4 || c5 || c6 || c7 || c8) (func { row = s0.row, column = s0.col } a b c d e f g h) s8 + ) + + +map9WithRange : (Range -> a -> b -> c -> d -> e -> f -> g -> h -> i -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser g -> Parser h -> Parser i -> Parser value +map9WithRange func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) (Parser parseG) (Parser parseH) (Parser parseI) = + Parser + (\s0 -> + case parseA s0 of + Bad committed x () -> + Bad committed x () + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + Bad (c1 || c2) x () + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + Bad (c1 || c2 || c3) x () + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + Bad (c1 || c2 || c3 || c4) x () + + Good c4 d s4 -> + case parseE s4 of + Bad c5 x () -> + Bad (c1 || c2 || c3 || c4 || c5) x () + + Good c5 e s5 -> + case parseF s5 of + Bad c6 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6) x () + + Good c6 f s6 -> + case parseG s6 of + Bad c7 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6 || c7) x () + + Good c7 g s7 -> + case parseH s7 of + Bad c8 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6 || c7 || c8) x () + + Good c8 h s8 -> + case parseI s8 of + Bad c9 x () -> + Bad (c1 || c2 || c3 || c4 || c5 || c6 || c7 || c8 || c9) x () + + Good c9 i s9 -> + Good (c1 || c2 || c3 || c4 || c5 || c6 || c7 || c8 || c9) (func { start = { row = s0.row, column = s0.col }, end = { row = s9.row, column = s9.col } } a b c d e f g h i) s9 + ) -map7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser g -> Parser value -map7 = - A.map7 +{-| Indicate that a parser has reached a dead end. "Everything was going fine +until I ran into this problem." Check out the -AndThen helpers for where to use this. +-} +problem : String -> Parser a +problem msg = + Parser (\s -> Bad False (ExpectingCustom s.row s.col msg) ()) -map8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser g -> Parser h -> Parser value -map8 = - A.map8 +orSucceed : Parser a -> a -> Parser a +orSucceed (Parser attemptFirst) secondRes = + Parser + (\s -> + case attemptFirst s of + (Good _ _ _) as firstGood -> + firstGood + (Bad firstCommitted _ ()) as firstBad -> + if firstCommitted then + firstBad -map9 : (a -> b -> c -> d -> e -> f -> g -> h -> i -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Parser f -> Parser g -> Parser h -> Parser i -> Parser value -map9 = - A.map9 + else + Good False secondRes s + ) -{-| Indicate that a parser has reached a dead end. "Everything was going fine -until I ran into this problem." Check out the [`andThen`](#andThen) docs to see -an example usage. --} -problem : String -> Parser a -problem msg = - A.problem (Parser.Problem msg) +mapOrSucceed : (a -> b) -> Parser a -> b -> Parser b +mapOrSucceed valueChange (Parser parse) fallback = + Parser + (\s0 -> + case parse s0 of + Good committed value s1 -> + Good committed (valueChange value) s1 + Bad firstCommitted x () -> + if firstCommitted then + Bad True x () + + else + Good False fallback s0 + ) + + +map2OrSucceed : (a -> b -> value) -> Parser a -> Parser b -> value -> Parser value +map2OrSucceed func (Parser parseA) (Parser parseB) fallback = + Parser + (\s0 -> + case parseA s0 of + Bad c1 x () -> + if c1 then + Bad True x () + + else + Good False fallback s0 + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + if c1 || c2 then + Bad True x () + + else + Good False fallback s0 + + Good c2 b s2 -> + Good (c1 || c2) (func a b) s2 + ) -orSucceed : Parser a -> a -> Parser a -orSucceed = - A.orSucceed +map3OrSucceed : (a -> b -> c -> value) -> Parser a -> Parser b -> Parser c -> value -> Parser value +map3OrSucceed func (Parser parseA) (Parser parseB) (Parser parseC) fallback = + Parser + (\s0 -> + case parseA s0 of + Bad c1 x () -> + if c1 then + Bad True x () -orSucceedLazy : Parser a -> (() -> a) -> Parser a -orSucceedLazy = - A.orSucceedLazy + else + Good False fallback s0 + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + if c1 || c2 then + Bad True x () + + else + Good False fallback s0 + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + if c1 || c2 || c3 then + Bad True x () + + else + Good False fallback s0 + + Good c3 c s3 -> + Good (c1 || c2 || c3) (func a b c) s3 + ) + + +map4OrSucceed : (a -> b -> c -> d -> value) -> Parser a -> Parser b -> Parser c -> Parser d -> value -> Parser value +map4OrSucceed func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) fallback = + Parser + (\s0 -> + case parseA s0 of + Bad c1 x () -> + if c1 then + Bad True x () + + else + Good False fallback s0 + + Good c1 a s1 -> + case parseB s1 of + Bad c2 x () -> + if c1 || c2 then + Bad True x () + + else + Good False fallback s0 + + Good c2 b s2 -> + case parseC s2 of + Bad c3 x () -> + if c1 || c2 || c3 then + Bad True x () + + else + Good False fallback s0 + + Good c3 c s3 -> + case parseD s3 of + Bad c4 x () -> + if c1 || c2 || c3 || c4 then + Bad True x () + + else + Good False fallback s0 + + Good c4 d s4 -> + Good (c1 || c2 || c3 || c4) (func a b c d) s4 + ) + + +oneOf2Map : + (first -> choice) + -> Parser first + -> (second -> choice) + -> Parser second + -> Parser choice +oneOf2Map firstToChoice (Parser attemptFirst) secondToChoice (Parser attemptSecond) = + Parser + (\s -> + case attemptFirst s of + Good firstCommitted first s1 -> + Good firstCommitted (firstToChoice first) s1 + + Bad firstCommitted firstX () -> + if firstCommitted then + Bad firstCommitted firstX () + + else + case attemptSecond s of + Good secondCommitted second s1 -> + Good secondCommitted (secondToChoice second) s1 + + Bad secondCommitted secondX () -> + if secondCommitted then + Bad secondCommitted secondX () + + else + Bad False (ExpectingOneOf firstX secondX []) () + ) + + +oneOf2MapWithStartRowColumnAndEndRowColumn : + (Int -> Int -> first -> Int -> Int -> choice) + -> Parser first + -> (Int -> Int -> second -> Int -> Int -> choice) + -> Parser second + -> Parser choice +oneOf2MapWithStartRowColumnAndEndRowColumn firstToChoice (Parser attemptFirst) secondToChoice (Parser attemptSecond) = + Parser + (\s -> + case attemptFirst s of + Good firstCommitted first s1 -> + Good firstCommitted + (firstToChoice s.row s.col first s1.row s1.col) + s1 + + Bad firstCommitted firstX () -> + if firstCommitted then + Bad firstCommitted firstX () + + else + case attemptSecond s of + Good secondCommitted second s1 -> + Good secondCommitted + (secondToChoice s.row s.col second s1.row s1.col) + s1 + + Bad secondCommitted secondX () -> + if secondCommitted then + Bad secondCommitted secondX () + + else + Bad False (ExpectingOneOf firstX secondX []) () + ) oneOf2 : Parser a -> Parser a -> Parser a -oneOf2 = - A.oneOf2 +oneOf2 (Parser attemptFirst) (Parser attemptSecond) = + Parser + (\s -> + case attemptFirst s of + (Good _ _ _) as firstGood -> + firstGood + (Bad firstCommitted firstX ()) as firstBad -> + if firstCommitted then + firstBad -{-| If you are parsing JSON, the values can be strings, floats, booleans, -arrays, objects, or null. You need a way to pick `oneOf` them! Here is a -sample of what that code might look like: + else + case attemptSecond s of + (Good _ _ _) as secondGood -> + secondGood - type Json - = Number Float - | Boolean Bool - | Null + (Bad secondCommitted secondX ()) as secondBad -> + if secondCommitted then + secondBad - json : Parser Json - json = - oneOf - [ map Number float - , map (\_ -> Boolean True) (keyword "true") - , map (\_ -> Boolean False) (keyword "false") - , map (\_ -> Null) keyword "null" - ] + else + Bad False (ExpectingOneOf firstX secondX []) () + ) -This parser will keep trying parsers until `oneOf` them starts chomping -characters. Once a path is chosen, it does not come back and try the others. -**Note:** I highly recommend reading [this document][semantics] to learn how -`oneOf` and `backtrackable` interact. It is subtle and important! +oneOf2OrSucceed : Parser a -> Parser a -> a -> Parser a +oneOf2OrSucceed (Parser attemptFirst) (Parser attemptSecond) thirdRes = + Parser + (\s -> + case attemptFirst s of + (Good _ _ _) as firstGood -> + firstGood -[semantics]: https://github.com/elm/parser/blob/master/semantics.md + (Bad firstCommitted _ ()) as firstBad -> + if firstCommitted then + firstBad --} -oneOf : List (Parser a) -> Parser a -oneOf = - A.oneOf + else + case attemptSecond s of + (Good _ _ _) as secondGood -> + secondGood + (Bad secondCommitted _ ()) as secondBad -> + if secondCommitted then + secondBad -{-| Transform the result of a parser. Maybe you have a value that is -an integer or `null`: + else + Good False thirdRes s + ) - nullOrInt : Parser (Maybe Int) - nullOrInt = - oneOf - [ map Just int - , map (\_ -> Nothing) (keyword "null") - ] +oneOf3 : Parser a -> Parser a -> Parser a -> Parser a +oneOf3 (Parser attemptFirst) (Parser attemptSecond) (Parser attemptThird) = + Parser + (\s -> + case attemptFirst s of + (Good _ _ _) as firstGood -> + firstGood - -- run nullOrInt "0" == Ok (Just 0) - -- run nullOrInt "13" == Ok (Just 13) - -- run nullOrInt "null" == Ok Nothing - -- run nullOrInt "zero" == Err ... + (Bad firstCommitted firstX ()) as firstBad -> + if firstCommitted then + firstBad --} -map : (a -> b) -> Parser a -> Parser b -map = - A.map + else + case attemptSecond s of + (Good _ _ _) as secondGood -> + secondGood + + (Bad secondCommitted secondX ()) as secondBad -> + if secondCommitted then + secondBad + + else + case attemptThird s of + (Good _ _ _) as thirdGood -> + thirdGood + + (Bad thirdCommitted thirdX ()) as thirdBad -> + if thirdCommitted then + thirdBad + + else + Bad False (ExpectingOneOf firstX secondX [ thirdX ]) () + ) + + +oneOf4 : Parser a -> Parser a -> Parser a -> Parser a -> Parser a +oneOf4 (Parser attemptFirst) (Parser attemptSecond) (Parser attemptThird) (Parser attemptFourth) = + Parser + (\s -> + case attemptFirst s of + (Good _ _ _) as firstGood -> + firstGood + + (Bad firstCommitted firstX ()) as firstBad -> + if firstCommitted then + firstBad + + else + case attemptSecond s of + (Good _ _ _) as secondGood -> + secondGood + + (Bad secondCommitted secondX ()) as secondBad -> + if secondCommitted then + secondBad + + else + case attemptThird s of + (Good _ _ _) as thirdGood -> + thirdGood + + (Bad thirdCommitted thirdX ()) as thirdBad -> + if thirdCommitted then + thirdBad + + else + case attemptFourth s of + (Good _ _ _) as fourthGood -> + fourthGood + + (Bad fourthCommitted fourthX ()) as fourthBad -> + if fourthCommitted then + fourthBad + else + Bad False (ExpectingOneOf firstX secondX [ thirdX, fourthX ]) () + ) -{-| It is quite tricky to use `backtrackable` well! It can be very useful, but -also can degrade performance and error message quality. -Read [this document](https://github.com/elm/parser/blob/master/semantics.md) -to learn how `oneOf`, `backtrackable`, and `commit` work and interact with -each other. It is subtle and important! +oneOf5 : Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a +oneOf5 (Parser attemptFirst) (Parser attemptSecond) (Parser attemptThird) (Parser attemptFourth) (Parser attemptFifth) = + Parser + (\s -> + case attemptFirst s of + (Good _ _ _) as firstGood -> + firstGood + (Bad firstCommitted firstX ()) as firstBad -> + if firstCommitted then + firstBad + + else + case attemptSecond s of + (Good _ _ _) as secondGood -> + secondGood + + (Bad secondCommitted secondX ()) as secondBad -> + if secondCommitted then + secondBad + + else + case attemptThird s of + (Good _ _ _) as thirdGood -> + thirdGood + + (Bad thirdCommitted thirdX ()) as thirdBad -> + if thirdCommitted then + thirdBad + + else + case attemptFourth s of + (Good _ _ _) as fourthGood -> + fourthGood + + (Bad fourthCommitted fourthX ()) as fourthBad -> + if fourthCommitted then + fourthBad + + else + case attemptFifth s of + (Good _ _ _) as fifthGood -> + fifthGood + + (Bad fifthCommitted fifthX ()) as fifthBad -> + if fifthCommitted then + fifthBad + + else + Bad False (ExpectingOneOf firstX secondX [ thirdX, fourthX, fifthX ]) () + ) + + +oneOf7 : Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a +oneOf7 (Parser attempt0) (Parser attempt1) (Parser attempt2) (Parser attempt3) (Parser attempt4) (Parser attempt5) (Parser attempt6) = + Parser + (\s -> + case attempt0 s of + (Good _ _ _) as good -> + good + + (Bad committed0 x0 ()) as bad0 -> + if committed0 then + bad0 + + else + case attempt1 s of + (Good _ _ _) as good -> + good + + (Bad committed1 x1 ()) as bad1 -> + if committed1 then + bad1 + + else + case attempt2 s of + (Good _ _ _) as good -> + good + + (Bad committed2 x2 ()) as bad2 -> + if committed2 then + bad2 + + else + case attempt3 s of + (Good _ _ _) as good -> + good + + (Bad committed3 x3 ()) as bad3 -> + if committed3 then + bad3 + + else + case attempt4 s of + (Good _ _ _) as good -> + good + + (Bad committed4 x4 ()) as bad4 -> + if committed4 then + bad4 + + else + case attempt5 s of + (Good _ _ _) as good -> + good + + (Bad committed5 x5 ()) as bad5 -> + if committed5 then + bad5 + + else + case attempt6 s of + (Good _ _ _) as good -> + good + + (Bad committed6 x6 ()) as bad6 -> + if committed6 then + bad6 + + else + Bad False (ExpectingOneOf x0 x1 [ x2, x3, x4, x5, x6 ]) () + ) + + +oneOf10 : Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a +oneOf10 (Parser attempt0) (Parser attempt1) (Parser attempt2) (Parser attempt3) (Parser attempt4) (Parser attempt5) (Parser attempt6) (Parser attempt7) (Parser attempt8) (Parser attempt9) = + Parser + (\s -> + case attempt0 s of + (Good _ _ _) as good -> + good + + (Bad committed0 x0 ()) as bad0 -> + if committed0 then + bad0 + + else + case attempt1 s of + (Good _ _ _) as good -> + good + + (Bad committed1 x1 ()) as bad1 -> + if committed1 then + bad1 + + else + case attempt2 s of + (Good _ _ _) as good -> + good + + (Bad committed2 x2 ()) as bad2 -> + if committed2 then + bad2 + + else + case attempt3 s of + (Good _ _ _) as good -> + good + + (Bad committed3 x3 ()) as bad3 -> + if committed3 then + bad3 + + else + case attempt4 s of + (Good _ _ _) as good -> + good + + (Bad committed4 x4 ()) as bad4 -> + if committed4 then + bad4 + + else + case attempt5 s of + (Good _ _ _) as good -> + good + + (Bad committed5 x5 ()) as bad5 -> + if committed5 then + bad5 + + else + case attempt6 s of + (Good _ _ _) as good -> + good + + (Bad committed6 x6 ()) as bad6 -> + if committed6 then + bad6 + + else + case attempt7 s of + (Good _ _ _) as good -> + good + + (Bad committed7 x7 ()) as bad7 -> + if committed7 then + bad7 + + else + case attempt8 s of + (Good _ _ _) as good -> + good + + (Bad committed8 x8 ()) as bad8 -> + if committed8 then + bad8 + + else + case attempt9 s of + (Good _ _ _) as good -> + good + + (Bad committed9 x9 ()) as bad9 -> + if committed9 then + bad9 + + else + Bad False (ExpectingOneOf x0 x1 [ x2, x3, x4, x5, x6, x7, x8, x9 ]) () + ) + + +oneOf14 : Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a -> Parser a +oneOf14 (Parser attempt0) (Parser attempt1) (Parser attempt2) (Parser attempt3) (Parser attempt4) (Parser attempt5) (Parser attempt6) (Parser attempt7) (Parser attempt8) (Parser attempt9) (Parser attempt10) (Parser attempt11) (Parser attempt12) (Parser attempt13) = + Parser + (\s -> + case attempt0 s of + (Good _ _ _) as good -> + good + + (Bad committed0 x0 ()) as bad0 -> + if committed0 then + bad0 + + else + case attempt1 s of + (Good _ _ _) as good -> + good + + (Bad committed1 x1 ()) as bad1 -> + if committed1 then + bad1 + + else + case attempt2 s of + (Good _ _ _) as good -> + good + + (Bad committed2 x2 ()) as bad2 -> + if committed2 then + bad2 + + else + case attempt3 s of + (Good _ _ _) as good -> + good + + (Bad committed3 x3 ()) as bad3 -> + if committed3 then + bad3 + + else + case attempt4 s of + (Good _ _ _) as good -> + good + + (Bad committed4 x4 ()) as bad4 -> + if committed4 then + bad4 + + else + case attempt5 s of + (Good _ _ _) as good -> + good + + (Bad committed5 x5 ()) as bad5 -> + if committed5 then + bad5 + + else + case attempt6 s of + (Good _ _ _) as good -> + good + + (Bad committed6 x6 ()) as bad6 -> + if committed6 then + bad6 + + else + case attempt7 s of + (Good _ _ _) as good -> + good + + (Bad committed7 x7 ()) as bad7 -> + if committed7 then + bad7 + + else + case attempt8 s of + (Good _ _ _) as good -> + good + + (Bad committed8 x8 ()) as bad8 -> + if committed8 then + bad8 + + else + case attempt9 s of + (Good _ _ _) as good -> + good + + (Bad committed9 x9 ()) as bad9 -> + if committed9 then + bad9 + + else + case attempt10 s of + (Good _ _ _) as good -> + good + + (Bad committed10 x10 ()) as bad10 -> + if committed10 then + bad10 + + else + case attempt11 s of + (Good _ _ _) as good -> + good + + (Bad committed11 x11 ()) as bad11 -> + if committed11 then + bad11 + + else + case attempt12 s of + (Good _ _ _) as good -> + good + + (Bad committed12 x12 ()) as bad12 -> + if committed12 then + bad12 + + else + case attempt13 s of + (Good _ _ _) as good -> + good + + (Bad committed13 x13 ()) as bad13 -> + if committed13 then + bad13 + + else + Bad False (ExpectingOneOf x0 x1 [ x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13 ]) () + ) + + +{-| Try a dynamic or large list of possibilities where existing oneOfN helpers aren't enough. -} -backtrackable : Parser a -> Parser a -backtrackable = - A.backtrackable +oneOf : List (Parser a) -> Parser a +oneOf possibilities = + case possibilities of + [] -> + Parser (\s -> Bad False (ExpectingNonEmptyOneOf s.row s.col ()) ()) + + [ onlyPossibility ] -> + onlyPossibility + + (Parser parseFirst) :: (Parser parseSecond) :: remainingParsers -> + Parser + (\s -> + case parseFirst s of + (Good _ _ _) as good -> + good + + (Bad firstCommitted firstX ()) as firstBad -> + if firstCommitted then + firstBad + + else + case parseSecond s of + (Good _ _ _) as good -> + good + + (Bad secondCommitted secondX ()) as secondBad -> + if secondCommitted then + secondBad + + else + oneOfHelp s firstX secondX [] remainingParsers + ) +oneOfHelp : State -> Problem -> Problem -> List Problem -> List (Parser a) -> PStep a +oneOfHelp s0 firstX secondX remainingProblemsSoFar parsers = + case parsers of + [] -> + Bad False (ExpectingOneOf firstX secondX remainingProblemsSoFar) () --- TOKEN + (Parser parse) :: remainingParsers -> + case parse s0 of + (Good _ _ _) as good -> + good + (Bad committed x ()) as bad -> + if committed then + bad -{-| Parse a bunch of different kinds of numbers without backtracking. A parser -for Elm would need to handle integers, floats, and hexadecimal like this: + else + oneOfHelp s0 firstX secondX (x :: remainingProblemsSoFar) remainingParsers - type Expr - = Variable String - | Int Int - | Float Float - | Apply Expr Expr - elmNumber : Parser Expr - elmNumber = - number - { int = Just Int - , hex = Just Int -- 0x001A is allowed - , octal = Nothing -- 0o0731 is not - , binary = Nothing -- 0b1101 is not - , float = Just Float - } +{-| Transform the successful result of a parser. -If you wanted to implement a float parser, it would be like this: +Maybe you have a value that is an integer or `null`: - float : Parser Float - float = - number - { int = Just toFloat - , hex = Nothing - , octal = Nothing - , binary = Nothing - , float = Just identity - } -Notice that it actually is processing `int` results! This is because `123` -looks like an integer to me, but maybe it looks like a float to you. If you had -`int = Nothing`, floats would need a decimal like `1.0` in every case. If you -like explicitness, that may actually be preferable! + nullOrInt : Parser (Maybe Int) + nullOrInt = + ParserFast.oneOf2 + (ParserFast.map Just int) + (ParserFast.keyword "null" Nothing) -**Note:** This function does not check for weird trailing characters in the -current implementation, so parsing `123abc` can succeed up to `123` and then -move on. This is helpful for people who want to parse things like `40px` or -`3m`, but it requires a bit of extra code to rule out trailing characters in -other cases. + -- run nullOrInt "0" == Ok (Just 0) + -- run nullOrInt "13" == Ok (Just 13) + -- run nullOrInt "null" == Ok Nothing + -- run nullOrInt "zero" == Err ... -} -number : - { binary : Maybe (Int -> a) - , float : Maybe (Float -> a) - , hex : Maybe (Int -> a) - , int : Maybe (Int -> a) - , octal : Maybe (Int -> a) +map : (a -> b) -> Parser a -> Parser b +map func (Parser parse) = + Parser + (\s0 -> + case parse s0 of + Good committed a s1 -> + Good committed (func a) s1 + + Bad committed x () -> + Bad committed x () + ) + + +loopWhileSucceeds : Parser element -> folded -> (element -> folded -> folded) -> (folded -> res) -> Parser res +loopWhileSucceeds element initialFolded reduce foldedToRes = + Parser + (\s -> loopWhileSucceedsHelp False element initialFolded reduce foldedToRes s) + + +loopWhileSucceedsHelp : Bool -> Parser element -> folded -> (element -> folded -> folded) -> (folded -> res) -> State -> PStep res +loopWhileSucceedsHelp committedSoFar ((Parser parseElement) as element) soFar reduce foldedToRes s0 = + case parseElement s0 of + Good elementCommitted elementResult s1 -> + loopWhileSucceedsHelp (committedSoFar || elementCommitted) + element + (soFar |> reduce elementResult) + reduce + foldedToRes + s1 + + Bad elementCommitted x () -> + if elementCommitted then + Bad True x () + + else + Good committedSoFar (foldedToRes soFar) s0 + + +loopUntil : Parser () -> Parser element -> folded -> (element -> folded -> folded) -> (folded -> res) -> Parser res +loopUntil endParser element initialFolded reduce foldedToRes = + Parser + (\s -> loopUntilHelp False endParser element initialFolded reduce foldedToRes s) + + +loopUntilHelp : Bool -> Parser () -> Parser element -> folded -> (element -> folded -> folded) -> (folded -> res) -> State -> PStep res +loopUntilHelp committedSoFar ((Parser parseEnd) as endParser) ((Parser parseElement) as element) soFar reduce foldedToRes s0 = + case parseEnd s0 of + Good endCommitted () s1 -> + Good (committedSoFar || endCommitted) (foldedToRes soFar) s1 + + Bad endCommitted endX () -> + if endCommitted then + Bad True endX () + + else + case parseElement s0 of + Good elementCommitted elementResult s1 -> + loopUntilHelp (committedSoFar || elementCommitted) + endParser + element + (soFar |> reduce elementResult) + reduce + foldedToRes + s1 + + Bad elementCommitted x () -> + Bad (committedSoFar || elementCommitted) x () + + +numberHelp : + { binary : Result () (Int -> Range -> a) + , expecting : () + , float : Result () (Float -> Range -> a) + , hex : Result () (Int -> Range -> a) + , int : Result () (Int -> Range -> a) + , invalid : () + , octal : Result () (Int -> Range -> a) } -> Parser a -number i = - A.number - { binary = Result.fromMaybe Parser.ExpectingBinary i.binary - , expecting = Parser.ExpectingNumber - , float = Result.fromMaybe Parser.ExpectingFloat i.float - , hex = Result.fromMaybe Parser.ExpectingHex i.hex - , int = Result.fromMaybe Parser.ExpectingInt i.int - , invalid = Parser.ExpectingNumber - , octal = Result.fromMaybe Parser.ExpectingOctal i.octal - } +numberHelp consumers = + let + parserAdvancedNumberAndStringLength : Parser.Advanced.Parser c () { length : Int, number : Range -> a } + parserAdvancedNumberAndStringLength = + Parser.Advanced.map (\n -> \endOffset -> { length = endOffset, number = n }) + (Parser.Advanced.number consumers) + |= Parser.Advanced.getOffset + in + Parser + (\state -> + if String.any Char.isDigit (String.slice state.offset (state.offset + 1) state.src) then + case Parser.Advanced.run parserAdvancedNumberAndStringLength (String.slice state.offset (String.length state.src) state.src) of + Ok result -> + Good False + (result.number + { start = { row = state.row, column = state.col } + , end = { row = state.row, column = state.col + result.length } + } + ) + (stateAddLengthToOffsetAndColumn result.length state) + + Err _ -> + Bad False (ExpectingNumber state.row state.col ()) () + + else + Bad False (ExpectingNumber state.row state.col ()) () + ) + + +stateAddLengthToOffsetAndColumn : Int -> State -> State +stateAddLengthToOffsetAndColumn lengthAdded s = + { src = s.src + , offset = s.offset + lengthAdded + , indent = s.indent + , row = s.row + , col = s.col + lengthAdded + } {-| Parse integers. @@ -486,236 +1820,640 @@ parser like this: , int ] -**Note:** If you want a parser for both `Int` and `Float` literals, check out -[`number`](#number) below. It will be faster than using `oneOf` to combining -`int` and `float` yourself. - -} int : Parser Int int = - A.number - { binary = Err Parser.ExpectingInt - , expecting = Parser.ExpectingInt - , float = Err Parser.ExpectingInt - , hex = Err Parser.ExpectingInt - , int = Ok identity - , invalid = Parser.ExpectingInt - , octal = Err Parser.ExpectingInt + numberHelp + { binary = Err () + , expecting = () + , float = Err () + , hex = Err () + , int = Ok (\n _ -> n) + , invalid = () + , octal = Err () + } + + +floatOrIntOrHexMapWithRange : (Float -> Range -> a) -> (Int -> Range -> a) -> (Int -> Range -> a) -> Parser a +floatOrIntOrHexMapWithRange floatf intf hexf = + numberHelp + { binary = Err () + , expecting = () + , float = Ok floatf + , hex = Ok hexf + , int = Ok intf + , invalid = () + , octal = Err () } -{-| Parse symbols like `(` and `,`. -Make sure to never call with String "", as this will then always commit. +intOrHexMapWithRange : (Int -> Range -> a) -> (Int -> Range -> a) -> Parser a +intOrHexMapWithRange intf hexf = + numberHelp + { binary = Err () + , expecting = () + , float = Err () + , hex = Ok hexf + , int = Ok intf + , invalid = () + , octal = Err () + } + - run (symbol "[") "[" == Ok () - run (symbol "[") "4" == Err ... (ExpectingSymbol "[") ... +{-| Parse exact text like `(` and `,`. +Make sure the given String isn't empty and does not contain \\n +or 2-part UTF-16 characters. -**Note:** This is good for stuff like brackets and semicolons, but it probably -should not be used for binary operators like `+` and `-` because you can find + run (symbol "[" ()) "[" == Ok () + run (symbol "[" ()) "4" == Err ... (ExpectingSymbol "[") ... + +**Note:** This is good for stuff like brackets and semicolons, +but it might need extra validations for binary operators like `-` because you can find yourself in weird situations. For example, is `3--4` a typo? Or is it `3 - -4`? -I have had better luck with `chompWhile isSymbol` and sorting out which -operator it is afterwards. -} symbol : String -> res -> Parser res symbol str res = - A.symbol str (Parser.ExpectingSymbol str) res - + let + strLength : Int + strLength = + String.length str + in + Parser + (\s -> + let + newOffset : Int + newOffset = + s.offset + strLength + in + if String.slice s.offset newOffset s.src == str ++ "" then + Good True + res + { src = s.src + , offset = newOffset + , indent = s.indent + , row = s.row + , col = s.col + strLength + } + + else + Bad False (ExpectingSymbol s.row s.col str) () + ) + + +followedBySymbol : String -> Parser a -> Parser a +followedBySymbol str (Parser parsePrevious) = + let + strLength : Int + strLength = + String.length str + in + Parser + (\s0 -> + case parsePrevious s0 of + Good previousCommitted res s1 -> + let + newOffset : Int + newOffset = + s1.offset + strLength + in + if String.slice s1.offset newOffset s1.src == str ++ "" then + Good True + res + { src = s1.src + , offset = newOffset + , indent = s1.indent + , row = s1.row + , col = s1.col + strLength + } -{-| Make sure to never call with symbol "", as this will then always commit. + else + Bad previousCommitted (ExpectingSymbol s1.row s1.col str) () + + bad -> + bad + ) + + +symbolWithEndLocation : String -> (Location -> res) -> Parser res +symbolWithEndLocation str endLocationToRes = + let + strLength : Int + strLength = + String.length str + in + Parser + (\s -> + let + newOffset : Int + newOffset = + s.offset + strLength + in + if String.slice s.offset newOffset s.src == str ++ "" then + let + newCol : Int + newCol = + s.col + strLength + in + Good True + (endLocationToRes { row = s.row, column = newCol }) + { src = s.src + , offset = newOffset + , indent = s.indent + , row = s.row + , col = newCol + } + + else + Bad False (ExpectingSymbol s.row s.col str) () + ) + + +symbolWithRange : String -> (Range -> res) -> Parser res +symbolWithRange str startAndEndLocationToRes = + let + strLength : Int + strLength = + String.length str + in + Parser + (\s -> + let + newOffset : Int + newOffset = + s.offset + strLength + in + if String.slice s.offset newOffset s.src == str ++ "" then + let + newCol : Int + newCol = + s.col + strLength + in + Good True + (startAndEndLocationToRes { start = { row = s.row, column = s.col }, end = { row = s.row, column = newCol } }) + { src = s.src + , offset = newOffset + , indent = s.indent + , row = s.row + , col = newCol + } + + else + Bad False (ExpectingSymbol s.row s.col str) () + ) + + +{-| Make sure the given String isn't empty and does not contain \\n +or 2-part UTF-16 characters. -} symbolFollowedBy : String -> Parser next -> Parser next -symbolFollowedBy str nextParser = - A.symbolFollowedBy str (Parser.ExpectingSymbol str) nextParser - - -{-| Parse keywords like `let`, `case`, and `type`. -Make sure to never call with String "", as this will then always commit. - - run (keyword "let") "let" == Ok () - run (keyword "let") "var" == Err ... (ExpectingKeyword "let") ... - run (keyword "let") "letters" == Err ... (ExpectingKeyword "let") ... +symbolFollowedBy str (Parser parseNext) = + let + strLength : Int + strLength = + String.length str + in + Parser + (\s -> + let + newOffset : Int + newOffset = + s.offset + strLength + in + if String.slice s.offset newOffset s.src == str ++ "" then + parseNext + { src = s.src + , offset = newOffset + , indent = s.indent + , row = s.row + , col = s.col + strLength + } + |> pStepCommit + + else + Bad False (ExpectingSymbol s.row s.col str) () + ) + + +{-| Make sure the given String isn't empty and does not contain \\n +or 2-part UTF-16 characters. +-} +symbolBacktrackableFollowedBy : String -> Parser next -> Parser next +symbolBacktrackableFollowedBy str (Parser parseNext) = + let + strLength : Int + strLength = + String.length str + in + Parser + (\s -> + let + newOffset : Int + newOffset = + s.offset + strLength + in + if String.slice s.offset newOffset s.src == str ++ "" then + parseNext + { src = s.src + , offset = newOffset + , indent = s.indent + , row = s.row + , col = s.col + strLength + } + + else + Bad False (ExpectingSymbol s.row s.col str) () + ) + + +pStepCommit : PStep a -> PStep a +pStepCommit pStep = + case pStep of + Good _ a state -> + Good True a state + + Bad _ errors () -> + Bad True errors () + + +{-| Parse words without other word characters after like `let`, `case`, `type` or `import`. +Make sure the given String isn't empty and does not contain \\n +or 2-part UTF-16 characters. + + run (keyword "let" ()) "let" == Ok () + run (keyword "let" ()) "var" == Err ... (ExpectingKeyword "let") ... + run (keyword "let" ()) "letters" == Err ... (ExpectingKeyword "let") ... **Note:** Notice the third case there! `keyword` actually looks ahead one -character to make sure it is not a letter, number, or underscore. The goal is -to help with parsers like this: - - succeed identity - |> ParserFast.ignore keyword "let" - |> ParserFast.ignore spaces - |= elmVar - |> ParserFast.ignore spaces - |> ParserFast.ignore symbol "=" - -The trouble is that `spaces` may chomp zero characters (to handle expressions -like `[1,2]` and `[ 1 , 2 ]`) and in this case, it would mean `letters` could -be parsed as `let ters` and then wonder where the equals sign is! Check out the -[`symbol`](#symbol) docs if you need to customize this! +character to make sure it is not a letter, digit, or underscore. +This will help with the weird cases like +`case(x, y)` being totally fine but `casex` not being fine. -} keyword : String -> res -> Parser res keyword kwd res = - A.keyword kwd (Parser.ExpectingKeyword kwd) res - - -{-| Make sure to never call with String "", as this will then always commit. + let + kwdLength : Int + kwdLength = + String.length kwd + in + Parser + (\s -> + let + newOffset : Int + newOffset = + s.offset + kwdLength + in + if + (String.slice s.offset newOffset s.src == kwd ++ "") + && not (isSubCharAlphaNumOrUnderscore newOffset s.src) + then + Good True + res + { src = s.src + , offset = newOffset + , indent = s.indent + , row = s.row + , col = s.col + kwdLength + } + + else + Bad False (ExpectingKeyword s.row s.col kwd) () + ) + + +isSubCharAlphaNumOrUnderscore : Int -> String -> Bool +isSubCharAlphaNumOrUnderscore offset string = + String.any Char.Extra.isLatinAlphaNumOrUnderscoreFast + (String.slice offset (offset + 1) string) + + +{-| Make sure the given String isn't empty and does not contain \\n +or 2-part UTF-16 characters. -} keywordFollowedBy : String -> Parser next -> Parser next -keywordFollowedBy kwd nextParser = - A.keywordFollowedBy kwd (Parser.ExpectingKeyword kwd) nextParser +keywordFollowedBy kwd (Parser parseNext) = + let + kwdLength : Int + kwdLength = + String.length kwd + in + Parser + (\s -> + let + newOffset : Int + newOffset = + s.offset + kwdLength + in + if + (String.slice s.offset newOffset s.src == kwd ++ "") + && not (isSubCharAlphaNumOrUnderscore newOffset s.src) + then + parseNext + { src = s.src + , offset = newOffset + , indent = s.indent + , row = s.row + , col = s.col + kwdLength + } + |> pStepCommit + + else + Bad False (ExpectingKeyword s.row s.col kwd) () + ) {-| Check if you have reached the end of the string you are parsing. - - justAnInt : Parser Int - justAnInt = - succeed identity - |= int - |> ParserFast.ignore end - - -- run justAnInt "90210" == Ok 90210 - -- run justAnInt "1 + 2" == Err ... - -- run int "1 + 2" == Ok 1 - Parsers can succeed without parsing the whole string. Ending your parser with `end` guarantees that you have successfully parsed the whole string. +Typically you'd put one of these at the end of a file parser. + -} end : Parser () end = - A.end Parser.ExpectingEnd - - -{-| Sometimes parsers like `int` or `variable` cannot do exactly what you -need. The "chomping" family of functions is meant for that case! Maybe you -need to parse [valid PHP variables][php] like `$x` and `$txt`: - - php : Parser String - php = - getChompedString <| - succeed () - |> ParserFast.ignore chompIf (\c -> c == '$') - |> ParserFast.ignore chompIf (\c -> Char.isAlpha c || c == '_') - |> ParserFast.ignore chompWhile (\c -> Char.isAlphaNum c || c == '_') - -The idea is that you create a bunch of chompers that validate the underlying -characters. Then `getChompedString` extracts the underlying `String` efficiently. - -[php]: https://www.w3schools.com/php/php_variables.asp - --} -getChompedString : Parser a -> Parser String -getChompedString = - A.getChompedString - - -{-| This works just like [`getChompedString`](#getChompedString) but gives -a bit more flexibility. For example, maybe you want to parse Elm doc comments -and get (1) the full comment and (2) all of the names listed in the docs. - -You could implement `mapChompedString` like this: + Parser + (\s -> + if String.length s.src - s.offset == 0 then + Good False () s + + else + Bad False (ExpectingEnd s.row s.col ()) () + ) + + +anyChar : Parser Char +anyChar = + Parser + (\s -> + let + newOffset : Int + newOffset = + charOrEnd s.offset s.src + in + if newOffset == -1 then + -- end of source + Bad False (ExpectingAnyChar s.row s.col ()) () + + else if newOffset == -2 then + -- newline + Good True + '\n' + { src = s.src + , offset = s.offset + 1 + , indent = s.indent + , row = s.row + 1 + , col = 1 + } + + else + -- found + case String.toList (String.slice s.offset newOffset s.src) of + [] -> + Bad False (ExpectingAnyChar s.row s.col ()) () + + c :: _ -> + Good True + c + { src = s.src + , offset = newOffset + , indent = s.indent + , row = s.row + , col = s.col + 1 + } + ) + + +charOrEnd : Int -> String -> Int +charOrEnd offset string = + let + actualChar : String + actualChar = + String.slice offset (offset + 1) string + in + case actualChar of + "\n" -> + -2 + + "" -> + -1 + + _ -> + if charStringIsUtf16HighSurrogate actualChar then + offset + 2 + + else + offset + 1 + + +charStringIsUtf16HighSurrogate : String -> Bool +charStringIsUtf16HighSurrogate charString = + charString |> String.any Char.Extra.isUtf16Surrogate + + +whileMap : (Char -> Bool) -> (String -> res) -> Parser res +whileMap isGood chompedStringToRes = + Parser + (\s0 -> + let + s1 : State + s1 = + chompWhileHelp isGood s0.offset s0.row s0.col s0.src s0.indent + in + Good (s1.offset > s0.offset) + (chompedStringToRes (String.slice s0.offset s1.offset s0.src)) + s1 + ) + + +chompWhileHelp : (Char -> Bool) -> Int -> Int -> Int -> String -> Int -> State +chompWhileHelp isGood offset row col src indent = + let + actualChar : String + actualChar = + String.slice offset (offset + 1) src + in + if String.any isGood actualChar then + case actualChar of + "\n" -> + chompWhileHelp isGood (offset + 1) (row + 1) 1 src indent + + _ -> + chompWhileHelp isGood (offset + 1) row (col + 1) src indent + + else if + charStringIsUtf16HighSurrogate actualChar + && -- String.any iterates over code points (so here just one Char) + String.any isGood (String.slice offset (offset + 2) src) + then + chompWhileHelp isGood (offset + 2) row (col + 1) src indent + + else + -- no match + { src = src + , offset = offset + , indent = indent + , row = row + , col = col + } - mapChompedString : (String -> a -> b) -> Parser a -> Parser String - mapChompedString func parser = - succeed (\start value end src -> func (String.slice start end src) value) - |= getOffset - |= parser - |= getOffset - |= getSource --} -mapChompedString : (String -> a -> b) -> Parser a -> Parser b -mapChompedString = - A.mapChompedString +chompWhileWithoutLinebreakHelp : (Char -> Bool) -> Int -> Int -> Int -> String -> Int -> State +chompWhileWithoutLinebreakHelp isGood offset row col src indent = + let + actualChar : String + actualChar = + String.slice offset (offset + 1) src + in + if String.any isGood actualChar then + chompWhileWithoutLinebreakHelp isGood (offset + 1) row (col + 1) src indent + + else if + charStringIsUtf16HighSurrogate actualChar + && -- String.any iterates over code points (so here just one Char) + String.any isGood (String.slice offset (offset + 2) src) + then + chompWhileWithoutLinebreakHelp isGood (offset + 2) row (col + 1) src indent + + else + -- no match + { src = src + , offset = offset + , indent = indent + , row = row + , col = col + } -{-| Chomp one character if it passes the test. +followedByChompWhileWhitespace : Parser before -> Parser before +followedByChompWhileWhitespace (Parser parseBefore) = + Parser + (\s0 -> + case parseBefore s0 of + Good committed res s1 -> + let + s2 : State + s2 = + chompWhileWhitespaceHelp s1.offset s1.row s1.col s1.src s1.indent + in + Good (committed || s2.offset > s1.offset) res s2 - chompUpper : Parser () - chompUpper = - chompIf Char.isUpper + bad -> + bad + ) -So this can chomp a character like `T` and produces a `()` value. +{-| Match zero or more \\n, \\r and space characters, then proceed with the given parser -} -chompIf : (Char -> Bool) -> Parser () -chompIf isGood = - A.chompIf isGood Parser.UnexpectedChar +chompWhileWhitespaceFollowedBy : Parser next -> Parser next +chompWhileWhitespaceFollowedBy (Parser parseNext) = + Parser + (\s0 -> + let + s1 : State + s1 = + chompWhileWhitespaceHelp s0.offset s0.row s0.col s0.src s0.indent + in + if s1.offset > s0.offset then + parseNext s1 + |> pStepCommit + else + parseNext s1 + ) -chompIfFollowedBy : (Char -> Bool) -> Parser a -> Parser a -chompIfFollowedBy isGood nextParser = - A.chompIfFollowedBy isGood Parser.UnexpectedChar nextParser +chompWhileWhitespaceHelp : Int -> Int -> Int -> String -> Int -> State +chompWhileWhitespaceHelp offset row col src indent = + case String.slice offset (offset + 1) src of + " " -> + chompWhileWhitespaceHelp (offset + 1) row (col + 1) src indent -{-| Chomp zero or more characters if they pass the test. This is commonly -useful for chomping whitespace or variable names: + "\n" -> + chompWhileWhitespaceHelp (offset + 1) (row + 1) 1 src indent - whitespace : Parser () - whitespace = - chompWhile (\c -> c == ' ' || c == '\t' || c == '\n' || c == '\u{000D}') + "\u{000D}" -> + chompWhileWhitespaceHelp (offset + 1) row (col + 1) src indent - elmVar : Parser String - elmVar = - getChompedString <| - succeed () - |> ParserFast.ignore chompIf Char.isLower - |> ParserFast.ignore chompWhile (\c -> Char.isAlphaNum c || c == '_') - -**Note:** a `chompWhile` parser always succeeds! This can lead to tricky -situations, especially if you define your whitespace with it. In that case, -you could accidentally interpret `letx` as the keyword `let` followed by -"spaces" followed by the variable `x`. This is why the `keyword` and `number` -parsers peek ahead, making sure they are not followed by anything unexpected. - --} -chompWhile : (Char -> Bool) -> Parser () -chompWhile = - A.chompWhile + -- empty or non-whitespace + _ -> + { src = src, offset = offset, indent = indent, row = row, col = col } {-| Some languages are indentation sensitive. Python cares about tabs. Elm -cares about spaces sometimes. `withIndent` and `getIndent` allow you to manage -"indentation state" yourself, however is necessary in your scenario. +cares about spaces sometimes. Using `withIndent` in tandem with the validate/andThen helpers supplying indentation, +you can manage "indentation state" yourself, however is necessary in your scenario. @test-helper -} withIndent : Int -> Parser a -> Parser a -withIndent = - A.withIndent +withIndent newIndent (Parser parse) = + Parser + (\s0 -> + case parse (changeIndent newIndent s0) of + Good committed a s1 -> + Good committed a (changeIndent s0.indent s1) + + bad -> + bad + ) + + +changeIndent : Int -> State -> State +changeIndent newIndent s = + { src = s.src + , offset = s.offset + , indent = newIndent + , row = s.row + , col = s.col + } -{-| For a given ParserWithComments.Parser, take the current start column as indentation for the whole block +{-| For a given parser, take the current start column as indentation for the whole block +parsed by the given parser -} withIndentSetToColumn : Parser a -> Parser a -withIndentSetToColumn = - A.withIndentSetToColumn +withIndentSetToColumn (Parser parse) = + Parser + (\s0 -> + case parse (changeIndent s0.col s0) of + Good committed a s1 -> + Good committed a (changeIndent s0.indent s1) + bad -> + bad + ) -mapWithStartPosition : - (Location -> a -> b) - -> Parser a - -> Parser b -mapWithStartPosition = - A.mapWithStartPosition +withIndentSetToColumnMinus : Int -> Parser a -> Parser a +withIndentSetToColumnMinus columnToMoveIndentationBaseBackBy (Parser parse) = + Parser + (\s0 -> + case parse (changeIndent (s0.col - columnToMoveIndentationBaseBackBy) s0) of + Good committed a s1 -> + Good committed a (changeIndent s0.indent s1) -mapWithEndPosition : - (a -> Location -> b) - -> Parser a - -> Parser b -mapWithEndPosition = - A.mapWithEndPosition + bad -> + bad + ) -mapWithStartAndEndPosition : - (Location -> a -> Location -> b) +mapWithRange : + (Range -> a -> b) -> Parser a -> Parser b -mapWithStartAndEndPosition = - A.mapWithStartAndEndPosition +mapWithRange combineStartAndResult (Parser parse) = + Parser + (\s0 -> + case parse s0 of + Good committed a s1 -> + Good committed (combineStartAndResult { start = { row = s0.row, column = s0.col }, end = { row = s1.row, column = s1.col } } a) s1 + + Bad committed x () -> + Bad committed x () + ) {-| Create a parser for variables. If we wanted to parse type variables in Elm, @@ -727,77 +2465,401 @@ we could try something like this: typeVar : Parser String typeVar = - variable - { start = Char.isLower - , inner = \c -> Char.isAlphaNum c || c == '_' - , reserved = Set.fromList [ "let", "in", "case", "of" ] - } + ParserFast.ifFollowedByWhileValidateWithoutLinebreak + Char.isLower + (\c -> Char.isAlphaNum c || c == '_') + (\final -> final == "let" || final == "in" || final == "case" || final == "of") This is saying it _must_ start with a lower-case character. After that, -characters can be letters, numbers, or underscores. It is also saying that if -you run into any of these reserved names, it is definitely not a variable. +characters can be letters, digits, or underscores. It is also saying that if +you run into any of these reserved names after parsing as much as possible, +it is definitely not a variable. -} -variable : - { inner : Char -> Bool - , reserved : Set.Set String - , start : Char -> Bool - } +ifFollowedByWhileValidateWithoutLinebreak : + (Char -> Bool) + -> (Char -> Bool) + -> (String -> Bool) -> Parser String -variable i = - A.variable - { expecting = Parser.ExpectingVariable - , inner = i.inner - , reserved = i.reserved - , start = i.start - } - - -{-| Parse multi-line comments. So if you wanted to parse Elm whitespace or -JS whitespace, you could say: - - elm : Parser () - elm = - loop 0 <| - ifProgress <| - oneOf - [ lineComment "--" - , multiComment "{-" "-}" Nestable - , spaces - ] - - js : Parser () - js = - loop 0 <| - ifProgress <| - oneOf - [ lineComment "//" - , multiComment "/*" "*/" NotNestable - , chompWhile (\c -> c == ' ' || c == '\n' || c == '\u{000D}' || c == '\t') - ] - - ifProgress : Parser a -> Int -> Parser (Step Int ()) - ifProgress parser offset = - succeed identity - |> ParserFast.ignore parser - |= getOffset - |> map - (\newOffset -> - if offset == newOffset then - Done () +ifFollowedByWhileValidateWithoutLinebreak firstIsOkay afterFirstIsOkay resultIsOkay = + Parser + (\s -> + let + firstOffset : Int + firstOffset = + isSubCharWithoutLinebreak firstIsOkay s.offset s.src + in + if firstOffset == -1 then + Bad False (ExpectingCharSatisfyingPredicate s.row s.col ()) () + + else + let + s1 : State + s1 = + chompWhileWithoutLinebreakHelp afterFirstIsOkay firstOffset s.row (s.col + 1) s.src s.indent + + name : String + name = + String.slice s.offset s1.offset s.src + in + if resultIsOkay name then + Good True name s1 + + else + Bad False (ExpectingStringSatisfyingPredicate s.row (s.col + 1) ()) () + ) + + +ifFollowedByWhileValidateMapWithRangeWithoutLinebreak : + (Range -> String -> res) + -> (Char -> Bool) + -> (Char -> Bool) + -> (String -> Bool) + -> Parser res +ifFollowedByWhileValidateMapWithRangeWithoutLinebreak toResult firstIsOkay afterFirstIsOkay resultIsOkay = + Parser + (\s0 -> + let + firstOffset : Int + firstOffset = + isSubCharWithoutLinebreak firstIsOkay s0.offset s0.src + in + if firstOffset == -1 then + Bad False (ExpectingCharSatisfyingPredicate s0.row s0.col ()) () + + else + let + s1 : State + s1 = + chompWhileWithoutLinebreakHelp afterFirstIsOkay firstOffset s0.row (s0.col + 1) s0.src s0.indent + + name : String + name = + String.slice s0.offset s1.offset s0.src + in + if resultIsOkay name then + Good True (toResult { start = { row = s0.row, column = s0.col }, end = { row = s1.row, column = s1.col } } name) s1 + + else + Bad False (ExpectingStringSatisfyingPredicate s0.row (s0.col + 1) ()) () + ) + + +ifFollowedByWhileWithoutLinebreak : + (Char -> Bool) + -> (Char -> Bool) + -> Parser String +ifFollowedByWhileWithoutLinebreak firstIsOkay afterFirstIsOkay = + Parser + (\s -> + let + firstOffset : Int + firstOffset = + isSubCharWithoutLinebreak firstIsOkay s.offset s.src + in + if firstOffset == -1 then + Bad False (ExpectingCharSatisfyingPredicate s.row s.col ()) () + + else + let + s1 : State + s1 = + chompWhileWithoutLinebreakHelp afterFirstIsOkay firstOffset s.row (s.col + 1) s.src s.indent + in + Good True (String.slice s.offset s1.offset s.src) s1 + ) + + +ifFollowedByWhileMapWithRangeWithoutLinebreak : + (Range -> String -> res) + -> (Char -> Bool) + -> (Char -> Bool) + -> Parser res +ifFollowedByWhileMapWithRangeWithoutLinebreak rangeAndChompedToRes firstIsOkay afterFirstIsOkay = + Parser + (\s0 -> + let + firstOffset : Int + firstOffset = + isSubCharWithoutLinebreak firstIsOkay s0.offset s0.src + in + if firstOffset == -1 then + Bad False (ExpectingCharSatisfyingPredicate s0.row s0.col ()) () + + else + let + s1 : State + s1 = + chompWhileWithoutLinebreakHelp afterFirstIsOkay firstOffset s0.row (s0.col + 1) s0.src s0.indent + in + Good True + (rangeAndChompedToRes + { start = { row = s0.row, column = s0.col } + , end = { row = s1.row, column = s1.col } + } + (String.slice s0.offset s1.offset s0.src) + ) + s1 + ) + + +ifFollowedByWhileMapWithoutLinebreak : + (String -> res) + -> (Char -> Bool) + -> (Char -> Bool) + -> Parser res +ifFollowedByWhileMapWithoutLinebreak chompedToRes firstIsOkay afterFirstIsOkay = + Parser + (\s0 -> + let + firstOffset : Int + firstOffset = + isSubCharWithoutLinebreak firstIsOkay s0.offset s0.src + in + if firstOffset == -1 then + Bad False (ExpectingCharSatisfyingPredicate s0.row s0.col ()) () + + else + let + s1 : State + s1 = + chompWhileWithoutLinebreakHelp afterFirstIsOkay firstOffset s0.row (s0.col + 1) s0.src s0.indent + in + Good True + (chompedToRes (String.slice s0.offset s1.offset s0.src)) + s1 + ) + + +{-| Parse multi-line comments that can itself contain other arbirary multi-comments inside. +-} +nestableMultiCommentMapWithRange : (Range -> String -> res) -> ( Char, String ) -> ( Char, String ) -> Parser res +nestableMultiCommentMapWithRange rangeContentToRes ( openChar, openTail ) ( closeChar, closeTail ) = + let + open : String + open = + String.cons openChar openTail + + close : String + close = + String.cons closeChar closeTail + + isNotRelevant : Char -> Bool + isNotRelevant char = + char /= openChar && char /= closeChar && not (Char.Extra.isUtf16Surrogate char) + in + map2WithRange + (\range afterOpen contentAfterAfterOpen -> + rangeContentToRes + range + (open ++ afterOpen ++ contentAfterAfterOpen ++ close) + ) + (symbolFollowedBy open + (while isNotRelevant) + ) + (oneOf2 + (symbol close "") + (loop + ( "", 1 ) + (oneOf3 + (symbol close ( close, -1 )) + (symbol open ( open, 1 )) + (anyCharFollowedByWhileMap (\chomped -> ( chomped, 0 )) + isNotRelevant + ) + ) + (\( toAppend, nestingChange ) ( soFarContent, soFarNesting ) -> + let + newNesting : Int + newNesting = + soFarNesting + nestingChange + in + if newNesting == 0 then + Done soFarContent else - Loop newOffset + Loop ( soFarContent ++ toAppend ++ "", newNesting ) + ) + ) + ) + + +while : (Char -> Bool) -> Parser String +while isGood = + Parser + (\s0 -> + let + s1 : State + s1 = + chompWhileHelp isGood s0.offset s0.row s0.col s0.src s0.indent + in + Good (s1.offset > s0.offset) + (String.slice s0.offset s1.offset s0.src) + s1 + ) + + +whileWithoutLinebreak : (Char -> Bool) -> Parser String +whileWithoutLinebreak isGood = + Parser + (\s0 -> + let + s1 : State + s1 = + chompWhileWithoutLinebreakHelp isGood s0.offset s0.row s0.col s0.src s0.indent + in + Good (s1.offset > s0.offset) + (String.slice s0.offset s1.offset s0.src) + s1 + ) + + +anyCharFollowedByWhileMap : + (String -> res) + -> (Char -> Bool) + -> Parser res +anyCharFollowedByWhileMap chompedStringToRes afterFirstIsOkay = + Parser + (\s -> + let + firstOffset : Int + firstOffset = + charOrEnd s.offset s.src + in + if firstOffset == -1 then + -- end of source + Bad False (ExpectingAnyChar s.row s.col ()) () + + else + let + s1 : State + s1 = + if firstOffset == -2 then + chompWhileHelp afterFirstIsOkay (s.offset + 1) (s.row + 1) 1 s.src s.indent + + else + chompWhileHelp afterFirstIsOkay firstOffset s.row (s.col + 1) s.src s.indent + in + Good True (chompedStringToRes (String.slice s.offset s1.offset s.src)) s1 + ) + + +{-| Decide what steps to take next in your `loop`. + +If you are `Done`, you give the result of the whole `loop`. If you decide to +`Loop` around again, you give a new state to work from. Maybe you need to add +an item to a list? Or maybe you need to track some information about what you +just saw? + +**Note:** It may be helpful to learn about [finite-state machines][fsm] to get +a broader intuition about using `state`. I.e. You may want to create a `type` +that describes four possible states, and then use `Loop` to transition between +them as you consume characters. + +[fsm]: https://en.wikipedia.org/wiki/Finite-state_machine + +-} +type Step state a + = Loop state + | Done a + + +{-| A parser that can loop indefinitely. This can be helpful when parsing +repeated structures, like a bunch of statements: + + + statements : Parser (List Stmt) + statements = + loop maybeStatementSemicolonWhitespace + [] + (\step soFar -> + case step of + Just lastStatement -> + Loop (lastStatement :: soFar) + + Nothing -> + Done (List.reverse soFar) + ) + + maybeStatementSemicolonWhitespace : List Stmt -> Parser (Step (List Stmt) (List Stmt)) + maybeStatementSemicolonWhitespace revStmts = + orSucceed + (ParserFast.map Just + (statement + |> ParserFast.followedByChompWhileWhitespace + |> ParserFast.followedBySymbol ";" + |> ParserFast.followedByChompWhileWhitespace ) + ) + Nothing + + -- statement : Parser Stmt + +Notice that the statements are tracked in reverse as we `Loop`, and we reorder +them only once we are `Done`. This is a very common pattern with `loop`! + +**IMPORTANT NOTE:** Parsers like `while Char.isAlpha` can +succeed without consuming any characters. So in some cases you may want to e.g. +use an [`ifFollowedByWhileWithoutLinebreak`](#ifFollowedByWhileWithoutLinebreak) to ensure that each step actually consumed characters. +Otherwise you could end up in an infinite loop! + +You very likely don't need to keep track of specific state before deciding on how +to continue, so I recommend using one of the loop- helpers instead. + +-} +loop : state -> Parser extension -> (extension -> state -> Step state a) -> Parser a +loop state element reduce = + Parser + (\s -> loopHelp False state element reduce s) + + +loopHelp : Bool -> state -> Parser extension -> (extension -> state -> Step state a) -> State -> PStep a +loopHelp committedSoFar state ((Parser parseElement) as element) reduce s0 = + case parseElement s0 of + Good elementCommitted step s1 -> + case reduce step state of + Loop newState -> + loopHelp (committedSoFar || elementCommitted) newState element reduce s1 + + Done result -> + Good (committedSoFar || elementCommitted) result s1 + + Bad elementCommitted x () -> + Bad (committedSoFar || elementCommitted) x () + + +{-| When parsing, you want to allocate as little as possible. +So this function lets you say: + + isSubCharWithoutLinebreak isSpace offset "this is the source string" + --==> newOffset + +The `(Char -> Bool)` argument is called a predicate. +The `newOffset` value can be a few different things: -**Note:** The fact that `spaces` comes last in the definition of `elm` is very -important! It can succeed without consuming any characters, so if it were the -first option, it would always succeed and bypass the others! (Same is true of -`chompWhile` in `js`.) This possibility of success without consumption is also -why wee need the `ifProgress` helper. It detects if there is no more whitespace -to consume. + - `-1` means that the predicate failed + - otherwise you will get `offset + 1` or `offset + 2` + depending on whether the UTF16 character is one or two + words wide. -} -nestableMultiComment : String -> String -> Parser () -nestableMultiComment open close = - A.nestableMultiComment open (Parser.Expecting open) close (Parser.Expecting close) +isSubCharWithoutLinebreak : (Char -> Bool) -> Int -> String -> Int +isSubCharWithoutLinebreak predicate offset string = + -- https://github.com/elm/parser/blob/1.1.0/src/Elm/Kernel/Parser.js#L37 + let + actualChar : String + actualChar = + String.slice offset (offset + 1) string + in + if String.any predicate actualChar then + offset + 1 + + else if + charStringIsUtf16HighSurrogate actualChar + && -- String.any iterates over code points (so here just one Char) + String.any predicate (String.slice offset (offset + 2) string) + then + offset + 2 + + else + -1 diff --git a/src/ParserFast/Advanced.elm b/src/ParserFast/Advanced.elm deleted file mode 100644 index c172de2d..00000000 --- a/src/ParserFast/Advanced.elm +++ /dev/null @@ -1,1342 +0,0 @@ -module ParserFast.Advanced exposing - ( Parser, run - , number, symbol, symbolFollowedBy, keyword, keywordFollowedBy, variable, end - , succeed, problem, lazy, map, map2, map3, map4, map5, map6, map7, map8, map9, andThen, ignore - , orSucceed, orSucceedLazy, oneOf2, oneOf, backtrackable - , loop, Step(..) - , nestableMultiComment - , getChompedString, chompIf, chompIfFollowedBy, chompWhile, mapChompedString - , withIndent, withIndentSetToColumn - , columnAndThen, columnIndentAndThen, offsetSourceAndThen, mapWithStartPosition, mapWithEndPosition, mapWithStartAndEndPosition - ) - -{-| - -@docs Parser, run - -@docs number, symbol, symbolFollowedBy, keyword, keywordFollowedBy, variable, end - - -# Flow - -@docs succeed, problem, lazy, map, map2, map3, map4, map5, map6, map7, map8, map9, andThen, ignore - -@docs orSucceed, orSucceedLazy, oneOf2, oneOf, backtrackable - -@docs loop, Step - - -# Whitespace - -@docs nestableMultiComment - - -# Chompers - -@docs getChompedString, chompIf, chompIfFollowedBy, chompWhile, mapChompedString - - -# Indentation, Positions and Source - -@docs withIndent, withIndentSetToColumn -@docs columnAndThen, columnIndentAndThen, offsetSourceAndThen, mapWithStartPosition, mapWithEndPosition, mapWithStartAndEndPosition - --} - -import Char -import Char.Extra -import Elm.Syntax.Range exposing (Location) -import Parser.Advanced exposing ((|=)) -import Set - - - --- PARSERS - - -{-| An advanced `ParserFast` gives a way to improve your error messages: `problem`; -Instead of all errors being a `String`, you can create a -custom type like `type Problem = BadIndent | BadKeyword String` and track -problems much more precisely. - -I recommend starting with the simpler [`ParserFast`](ParserFast) module though, and -when you feel comfortable and want better error messages, you can create a type -alias like this: - - import Parser.Advanced - - type alias MyParser a = - Parser.Advanced.Parser Context Problem a - - type Context - = Definition String - | List - | Record - - type Problem - = BadIndent - | BadKeyword String - -All of the functions from `ParserFast` should exist in `ParserFast.Advanced` in some -form, allowing you to switch over pretty easily. - --} -type Parser problem value - = Parser (State -> PStep problem value) - - -type PStep problem value - = Good Bool value State - | Bad Bool (Bag problem) () - - -type alias State = - { src : String - , offset : Int - , indent : Int - , row : Int - , col : Int - } - - - --- RUN - - -{-| This works just like [`ParserFast.run`](ParserFast#run). -The only difference is that when it fails, it has much more precise information -for each dead end. --} -run : Parser x a -> String -> Result (List (DeadEnd x)) a -run (Parser parse) src = - case parse { src = src, offset = 0, indent = 1, row = 1, col = 1 } of - Good _ value _ -> - Ok value - - Bad _ bag () -> - Err (bagToList bag []) - - - --- PROBLEMS - - -{-| Say you are parsing a function named `viewHealthData` that contains a list. -You might get a `DeadEnd` like this: - - { row = 18 - , col = 22 - , problem = UnexpectedComma - , contextStack = - [ { row = 14 - , col = 1 - , context = Definition "viewHealthData" - } - , { row = 15 - , col = 4 - , context = List - } - ] - } - -We have a ton of information here! So in the error message, we can say that “I -ran into an issue when parsing a list in the definition of `viewHealthData`. It -looks like there is an extra comma.” Or maybe something even better! - -Furthermore, many parsers just put a mark where the problem manifested. By -tracking the `row` and `col` of the context, we can show a much larger region -as a way of indicating “I thought I was parsing this thing that starts over -here.” Otherwise you can get very confusing error messages on a missing `]` or -`}` or `)` because “I need more indentation” on something unrelated. - -**Note:** Rows and columns are counted like a text editor. The beginning is `row=1` -and `col=1`. The `col` increments as characters are chomped. When a `\n` is chomped, -`row` is incremented and `col` starts over again at `1`. - --} -type alias DeadEnd problem = - { row : Int - , col : Int - , problem : problem - } - - -type Bag x - = Empty - | AddRight (Bag x) (DeadEnd x) - | Append (Bag x) (Bag x) - - -fromState : State -> x -> Bag x -fromState s x = - AddRight Empty (DeadEnd s.row s.col x) - - -bagToList : Bag x -> List (DeadEnd x) -> List (DeadEnd x) -bagToList bag list = - case bag of - Empty -> - list - - AddRight bag1 x -> - bagToList bag1 (x :: list) - - Append bag1 bag2 -> - bagToList bag1 (bagToList bag2 list) - - -succeed : a -> Parser x a -succeed a = - Parser (\s -> Good False a s) - - -problem : x -> Parser x a -problem x = - Parser (\s -> Bad False (fromState s x) ()) - - -map : (a -> b) -> Parser x a -> Parser x b -map func (Parser parse) = - Parser - (\s0 -> - case parse s0 of - Good p a s1 -> - Good p (func a) s1 - - Bad p x () -> - Bad p x () - ) - - -map2 : (a -> b -> value) -> Parser x a -> Parser x b -> Parser x value -map2 func (Parser parseA) (Parser parseB) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - Good (p1 || p2) (func a b) s2 - ) - - -map3 : (a -> b -> c -> value) -> Parser x a -> Parser x b -> Parser x c -> Parser x value -map3 func (Parser parseA) (Parser parseB) (Parser parseC) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - case parseC s2 of - Bad p3 x () -> - Bad (p1 || p2 || p3) x () - - Good p3 c s3 -> - Good (p1 || p2 || p3) (func a b c) s3 - ) - - -map4 : (a -> b -> c -> d -> value) -> Parser x a -> Parser x b -> Parser x c -> Parser x d -> Parser x value -map4 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - case parseC s2 of - Bad p3 x () -> - Bad (p1 || p2 || p3) x () - - Good p3 c s3 -> - case parseD s3 of - Bad p4 x () -> - Bad (p1 || p2 || p3 || p4) x () - - Good p4 d s4 -> - Good (p1 || p2 || p3 || p4) (func a b c d) s4 - ) - - -map5 : (a -> b -> c -> d -> e -> value) -> Parser x a -> Parser x b -> Parser x c -> Parser x d -> Parser x e -> Parser x value -map5 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - case parseC s2 of - Bad p3 x () -> - Bad (p1 || p2 || p3) x () - - Good p3 c s3 -> - case parseD s3 of - Bad p4 x () -> - Bad (p1 || p2 || p3 || p4) x () - - Good p4 d s4 -> - case parseE s4 of - Bad p5 x () -> - Bad (p1 || p2 || p3 || p4 || p5) x () - - Good p5 e s5 -> - Good (p1 || p2 || p3 || p4 || p5) (func a b c d e) s5 - ) - - -map6 : (a -> b -> c -> d -> e -> f -> value) -> Parser x a -> Parser x b -> Parser x c -> Parser x d -> Parser x e -> Parser x f -> Parser x value -map6 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - case parseC s2 of - Bad p3 x () -> - Bad (p1 || p2 || p3) x () - - Good p3 c s3 -> - case parseD s3 of - Bad p4 x () -> - Bad (p1 || p2 || p3 || p4) x () - - Good p4 d s4 -> - case parseE s4 of - Bad p5 x () -> - Bad (p1 || p2 || p3 || p4 || p5) x () - - Good p5 e s5 -> - case parseF s5 of - Bad p6 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6) x () - - Good p6 f s6 -> - Good (p1 || p2 || p3 || p4 || p5 || p6) (func a b c d e f) s6 - ) - - -map7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Parser x a -> Parser x b -> Parser x c -> Parser x d -> Parser x e -> Parser x f -> Parser x g -> Parser x value -map7 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) (Parser parseG) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - case parseC s2 of - Bad p3 x () -> - Bad (p1 || p2 || p3) x () - - Good p3 c s3 -> - case parseD s3 of - Bad p4 x () -> - Bad (p1 || p2 || p3 || p4) x () - - Good p4 d s4 -> - case parseE s4 of - Bad p5 x () -> - Bad (p1 || p2 || p3 || p4 || p5) x () - - Good p5 e s5 -> - case parseF s5 of - Bad p6 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6) x () - - Good p6 f s6 -> - case parseG s6 of - Bad p7 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6 || p7) x () - - Good p7 g s7 -> - Good (p1 || p2 || p3 || p4 || p5 || p6 || p7) (func a b c d e f g) s7 - ) - - -map8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Parser x a -> Parser x b -> Parser x c -> Parser x d -> Parser x e -> Parser x f -> Parser x g -> Parser x h -> Parser x value -map8 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) (Parser parseG) (Parser parseH) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - case parseC s2 of - Bad p3 x () -> - Bad (p1 || p2 || p3) x () - - Good p3 c s3 -> - case parseD s3 of - Bad p4 x () -> - Bad (p1 || p2 || p3 || p4) x () - - Good p4 d s4 -> - case parseE s4 of - Bad p5 x () -> - Bad (p1 || p2 || p3 || p4 || p5) x () - - Good p5 e s5 -> - case parseF s5 of - Bad p6 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6) x () - - Good p6 f s6 -> - case parseG s6 of - Bad p7 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6 || p7) x () - - Good p7 g s7 -> - case parseH s7 of - Bad p8 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8) x () - - Good p8 h s8 -> - Good (p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8) (func a b c d e f g h) s8 - ) - - -map9 : (a -> b -> c -> d -> e -> f -> g -> h -> i -> value) -> Parser x a -> Parser x b -> Parser x c -> Parser x d -> Parser x e -> Parser x f -> Parser x g -> Parser x h -> Parser x i -> Parser x value -map9 func (Parser parseA) (Parser parseB) (Parser parseC) (Parser parseD) (Parser parseE) (Parser parseF) (Parser parseG) (Parser parseH) (Parser parseI) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - case parseC s2 of - Bad p3 x () -> - Bad (p1 || p2 || p3) x () - - Good p3 c s3 -> - case parseD s3 of - Bad p4 x () -> - Bad (p1 || p2 || p3 || p4) x () - - Good p4 d s4 -> - case parseE s4 of - Bad p5 x () -> - Bad (p1 || p2 || p3 || p4 || p5) x () - - Good p5 e s5 -> - case parseF s5 of - Bad p6 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6) x () - - Good p6 f s6 -> - case parseG s6 of - Bad p7 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6 || p7) x () - - Good p7 g s7 -> - case parseH s7 of - Bad p8 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8) x () - - Good p8 h s8 -> - case parseI s8 of - Bad p9 x () -> - Bad (p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8 || p9) x () - - Good p9 i s9 -> - Good (p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8 || p9) (func a b c d e f g h i) s9 - ) - - -ignore : Parser x keep -> Parser x ignore -> Parser x keep -ignore keepParser ignoreParser = - map2 always keepParser ignoreParser - - -andThen : (a -> Parser x b) -> Parser x a -> Parser x b -andThen callback (Parser parseA) = - Parser - (\s0 -> - case parseA s0 of - Bad p x () -> - Bad p x () - - Good p1 a s1 -> - let - (Parser parseB) = - callback a - in - case parseB s1 of - Bad p2 x () -> - Bad (p1 || p2) x () - - Good p2 b s2 -> - Good (p1 || p2) b s2 - ) - - -columnAndThen : (Int -> Parser x a) -> Parser x a -columnAndThen callback = - Parser - (\s -> - let - (Parser parse) = - callback s.col - in - parse s - ) - - -columnIndentAndThen : (Int -> Int -> Parser x a) -> Parser x a -columnIndentAndThen callback = - Parser - (\s -> - let - (Parser parse) = - callback s.col s.indent - in - parse s - ) - - -offsetSourceAndThen : (Int -> String -> Parser x a) -> Parser x a -offsetSourceAndThen callback = - Parser - (\s -> - let - (Parser parse) = - callback s.offset s.src - in - parse s - ) - - -lazy : (() -> Parser x a) -> Parser x a -lazy thunk = - Parser - (\s -> - let - (Parser parse) = - thunk () - in - parse s - ) - - -oneOf2 : Parser x a -> Parser x a -> Parser x a -oneOf2 (Parser attemptFirst) (Parser attemptSecond) = - Parser - (\s -> - case attemptFirst s of - (Good _ _ _) as firstPStep -> - firstPStep - - (Bad p0 firstX ()) as firstPStep -> - if p0 then - firstPStep - - else - case attemptSecond s of - (Good _ _ _) as secondPStep -> - secondPStep - - (Bad secondCommitted secondX ()) as secondPStep -> - if secondCommitted then - secondPStep - - else - Bad False (Append (Append Empty firstX) secondX) () - ) - - -orSucceed : Parser x a -> a -> Parser x a -orSucceed (Parser attemptFirst) secondRes = - Parser - (\s -> - case attemptFirst s of - (Good _ _ _) as firstPStep -> - firstPStep - - (Bad p0 _ ()) as firstPStep -> - if p0 then - firstPStep - - else - Good False secondRes s - ) - - -orSucceedLazy : Parser x a -> (() -> a) -> Parser x a -orSucceedLazy (Parser attemptFirst) createSecondRes = - Parser - (\s -> - case attemptFirst s of - (Good _ _ _) as firstPStep -> - firstPStep - - (Bad p0 _ ()) as firstPStep -> - if p0 then - firstPStep - - else - Good False (createSecondRes ()) s - ) - - -oneOf : List (Parser x a) -> Parser x a -oneOf parsers = - Parser (\s -> oneOfHelp s Empty parsers) - - -oneOfHelp : State -> Bag x -> List (Parser x a) -> PStep x a -oneOfHelp s0 bag parsers = - case parsers of - [] -> - Bad False bag () - - (Parser parse) :: remainingParsers -> - case parse s0 of - (Good _ _ _) as step -> - step - - (Bad p x ()) as step -> - if p then - step - - else - oneOfHelp s0 (Append bag x) remainingParsers - - -{-| Decide what steps to take next in your [`loop`](#loop). - -If you are `Done`, you give the result of the whole `loop`. If you decide to -`Loop` around again, you give a new state to work from. Maybe you need to add -an item to a list? Or maybe you need to track some information about what you -just saw? - -**Note:** It may be helpful to learn about [finite-state machines][fsm] to get -a broader intuition about using `state`. I.e. You may want to create a `type` -that describes four possible states, and then use `Loop` to transition between -them as you consume characters. - -[fsm]: https://en.wikipedia.org/wiki/Finite-state_machine - --} -type Step state a - = Loop state - | Done a - - -{-| A parser that can loop indefinitely. This can be helpful when parsing -repeated structures, like a bunch of statements: - - - statements : Parser (List Stmt) - statements = - loop [] statementsHelp - - statementsHelp : List Stmt -> Parser (Step (List Stmt) (List Stmt)) - statementsHelp revStmts = - oneOf - [ succeed (\stmt -> Loop (stmt :: revStmts)) - |= statement - |> ParserFast.ignore spaces - |> ParserFast.ignore symbol ";" - |> ParserFast.ignore spaces - , succeed () - |> map (\_ -> Done (List.reverse revStmts)) - ] - - -- statement : Parser Stmt - -Notice that the statements are tracked in reverse as we `Loop`, and we reorder -them only once we are `Done`. This is a very common pattern with `loop`! - -Check out [`examples/DoubleQuoteString.elm`](https://github.com/elm/parser/blob/master/examples/DoubleQuoteString.elm) -for another example. - -**IMPORTANT NOTE:** Parsers like `chompWhile Char.isAlpha` can -succeed without consuming any characters. So in some cases you may want to e.g. -prepend a [`chompIfFollowedBy`](#chompIfFollowedBy) to ensure that each step actually consumed characters. -Otherwise you could end up in an infinite loop! - -**Note:** Anything you can write with `loop`, you can also write as a parser -that chomps some characters `andThen` calls itself with new arguments. The -problem with calling `andThen` recursively is that it grows the stack, so you -cannot do it indefinitely. So `loop` is important because enables tail-call -elimination, allowing you to parse however many repeats you want. - --} -loop : state -> (state -> Parser x (Step state a)) -> Parser x a -loop state callback = - Parser - (\s -> loopHelp False state callback s) - - -loopHelp : Bool -> state -> (state -> Parser x (Step state a)) -> State -> PStep x a -loopHelp p state callback s0 = - let - (Parser parse) = - callback state - in - case parse s0 of - Good p1 step s1 -> - case step of - Loop newState -> - loopHelp (p || p1) newState callback s1 - - Done result -> - Good (p || p1) result s1 - - Bad p1 x () -> - Bad (p || p1) x () - - -backtrackable : Parser x a -> Parser x a -backtrackable (Parser parse) = - Parser - (\s0 -> - case parse s0 of - Bad _ x () -> - Bad False x () - - Good _ a s1 -> - Good False a s1 - ) - - -{-| Make sure to never call with String "", as this will then always commit. --} -keyword : String -> x -> res -> Parser x res -keyword kwd expecting res = - Parser - (\s -> - let - ( newOffset, newRow, newCol ) = - isSubString kwd s.offset s.row s.col s.src - in - if newOffset == -1 || 0 <= isSubChar (\c -> Char.Extra.isAlphaNumFast c || c == '_') newOffset s.src then - Bad False (fromState s expecting) () - - else - Good True - res - { src = s.src - , offset = newOffset - , indent = s.indent - , row = newRow - , col = newCol - } - ) - - -{-| Make sure to never call with String "", as this will then always commit. --} -keywordFollowedBy : String -> x -> Parser x next -> Parser x next -keywordFollowedBy kwd expecting (Parser parseNext) = - Parser - (\s -> - let - ( newOffset, newRow, newCol ) = - isSubString kwd s.offset s.row s.col s.src - in - if newOffset == -1 || 0 <= isSubChar (\c -> Char.Extra.isAlphaNumFast c || c == '_') newOffset s.src then - Bad False (fromState s expecting) () - - else - parseNext - { src = s.src - , offset = newOffset - , indent = s.indent - , row = newRow - , col = newCol - } - |> pStepCommit - ) - - -{-| Make sure to never call with String "", as this will then always commit. --} -symbol : String -> x -> res -> Parser x res -symbol str expecting res = - Parser - (\s -> - let - ( newOffset, newRow, newCol ) = - isSubString str s.offset s.row s.col s.src - in - if newOffset == -1 then - Bad False (fromState s expecting) () - - else - Good True - res - { src = s.src - , offset = newOffset - , indent = s.indent - , row = newRow - , col = newCol - } - ) - - -{-| Make sure to never call with String "", as this will then always commit. --} -symbolFollowedBy : String -> x -> Parser x next -> Parser x next -symbolFollowedBy str expecting (Parser parseNext) = - Parser - (\s -> - let - ( newOffset, newRow, newCol ) = - isSubString str s.offset s.row s.col s.src - in - if newOffset == -1 then - Bad False (fromState s expecting) () - - else - parseNext - { src = s.src - , offset = newOffset - , indent = s.indent - , row = newRow - , col = newCol - } - |> pStepCommit - ) - - -pStepCommit : PStep x a -> PStep x a -pStepCommit pStep = - case pStep of - Good _ a state -> - Good True a state - - Bad _ errors () -> - Bad True errors () - - -number : - { binary : Result x (Int -> a) - , expecting : x - , float : Result x (Float -> a) - , hex : Result x (Int -> a) - , int : Result x (Int -> a) - , invalid : x - , octal : Result x (Int -> a) - } - -> Parser x a -number c = - let - parserAdvancedNumberAndStringLength : Parser.Advanced.Parser c x { length : Int, number : a } - parserAdvancedNumberAndStringLength = - Parser.Advanced.map (\n -> \endOffset -> { length = endOffset, number = n }) - (Parser.Advanced.number c) - |= Parser.Advanced.getOffset - in - Parser - (\state -> - case Parser.Advanced.run parserAdvancedNumberAndStringLength (String.dropLeft state.offset state.src) of - Ok result -> - Good False result.number (bumpOffset (state.offset + result.length) state) - - Err _ -> - Bad False (fromState state c.invalid) () - ) - - -bumpOffset : Int -> State -> State -bumpOffset newOffset s = - { src = s.src - , offset = newOffset - , indent = s.indent - , row = s.row - , col = s.col + (newOffset - s.offset) - } - - -end : x -> Parser x () -end x = - Parser - (\s -> - if String.length s.src - s.offset == 0 then - Good False () s - - else - Bad False (fromState s x) () - ) - - -getChompedString : Parser x a -> Parser x String -getChompedString parser = - mapChompedString always parser - - -mapChompedString : (String -> a -> b) -> Parser x a -> Parser x b -mapChompedString func (Parser parse) = - Parser - (\s0 -> - case parse s0 of - Bad p x () -> - Bad p x () - - Good p a s1 -> - Good p (func (String.slice s0.offset s1.offset s0.src) a) s1 - ) - - -chompIfFollowedBy : (Char -> Bool) -> x -> Parser x a -> Parser x a -chompIfFollowedBy isGood expecting (Parser parseNext) = - Parser - (\s -> - let - newOffset : Int - newOffset = - isSubChar isGood s.offset s.src - in - if newOffset == -1 then - -- not found - Bad False (fromState s expecting) () - - else if newOffset == -2 then - -- newline - parseNext - { src = s.src - , offset = s.offset + 1 - , indent = s.indent - , row = s.row + 1 - , col = 1 - } - |> pStepCommit - - else - -- found - parseNext - { src = s.src - , offset = newOffset - , indent = s.indent - , row = s.row - , col = s.col + 1 - } - |> pStepCommit - ) - - -chompIf : (Char -> Bool) -> x -> Parser x () -chompIf isGood expecting = - Parser - (\s -> - let - newOffset : Int - newOffset = - isSubChar isGood s.offset s.src - in - if newOffset == -1 then - -- not found - Bad False (fromState s expecting) () - - else if newOffset == -2 then - -- newline - Good True - () - { src = s.src - , offset = s.offset + 1 - , indent = s.indent - , row = s.row + 1 - , col = 1 - } - - else - -- found - Good True - () - { src = s.src - , offset = newOffset - , indent = s.indent - , row = s.row - , col = s.col + 1 - } - ) - - -chompWhile : (Char -> Bool) -> Parser x () -chompWhile isGood = - Parser - (\s -> chompWhileHelp isGood s.offset s.row s.col s) - - -chompWhileHelp : (Char -> Bool) -> Int -> Int -> Int -> State -> PStep x () -chompWhileHelp isGood offset row col s0 = - let - newOffset : Int - newOffset = - isSubChar isGood offset s0.src - in - if newOffset == -1 then - -- no match - Good (s0.offset < offset) - () - { src = s0.src - , offset = offset - , indent = s0.indent - , row = row - , col = col - } - - else if newOffset == -2 then - -- matched a newline - chompWhileHelp isGood (offset + 1) (row + 1) 1 s0 - - else - -- normal match - chompWhileHelp isGood newOffset row (col + 1) s0 - - -variable : - { expecting : x - , inner : Char -> Bool - , reserved : Set.Set String - , start : Char -> Bool - } - -> Parser x String -variable i = - Parser - (\s -> - let - firstOffset : Int - firstOffset = - isSubChar i.start s.offset s.src - in - if firstOffset == -1 then - Bad False (fromState s i.expecting) () - - else - let - s1 : State - s1 = - if firstOffset == -2 then - varHelp i.inner (s.offset + 1) (s.row + 1) 1 s.src s.indent - - else - varHelp i.inner firstOffset s.row (s.col + 1) s.src s.indent - - name : String - name = - String.slice s.offset s1.offset s.src - in - if Set.member name i.reserved then - Bad False (fromState s i.expecting) () - - else - Good True name s1 - ) - - -varHelp : (Char -> Bool) -> Int -> Int -> Int -> String -> Int -> State -varHelp isGood offset row col src indent = - let - newOffset : Int - newOffset = - isSubChar isGood offset src - in - if newOffset == -1 then - { src = src - , offset = offset - , indent = indent - , row = row - , col = col - } - - else if newOffset == -2 then - varHelp isGood (offset + 1) (row + 1) 1 src indent - - else - varHelp isGood newOffset row (col + 1) src indent - - -skip : Parser x ignore -> Parser x keep -> Parser x keep -skip iParser kParser = - map2 revAlways iParser kParser - - -revAlways : a -> b -> b -revAlways _ b = - b - - -nestableMultiComment : String -> x -> String -> x -> Parser x () -nestableMultiComment oStr oX cStr cX = - case String.uncons oStr of - Nothing -> - problem oX - - Just ( openChar, _ ) -> - case String.uncons cStr of - Nothing -> - problem cX - - Just ( closeChar, _ ) -> - let - isNotRelevant : Char -> Bool - isNotRelevant char = - char /= openChar && char /= closeChar - - chompOpen : Parser x () - chompOpen = - symbol oStr oX () - in - ignore chompOpen (nestableHelp isNotRelevant chompOpen (symbol cStr cX ()) cX 1) - - -nestableHelp : (Char -> Bool) -> Parser x () -> Parser x () -> x -> Int -> Parser x () -nestableHelp isNotRelevant open close expectingClose nestLevel = - skip (chompWhile isNotRelevant) - (oneOf - [ if nestLevel == 1 then - close - - else - close - |> andThen (\() -> nestableHelp isNotRelevant open close expectingClose (nestLevel - 1)) - , open - |> andThen (\() -> nestableHelp isNotRelevant open close expectingClose (nestLevel + 1)) - , chompIf isChar expectingClose - |> andThen - (\() -> - nestableHelp isNotRelevant open close expectingClose nestLevel - ) - ] - ) - - -isChar : Char -> Bool -isChar _ = - True - - -withIndent : Int -> Parser x a -> Parser x a -withIndent newIndent (Parser parse) = - Parser - (\s0 -> - case parse (changeIndent newIndent s0) of - Good p a s1 -> - Good p a (changeIndent s0.indent s1) - - Bad p x () -> - Bad p x () - ) - - -withIndentSetToColumn : Parser x a -> Parser x a -withIndentSetToColumn (Parser parse) = - Parser - (\s0 -> - case parse (changeIndent s0.col s0) of - Good p a s1 -> - Good p a (changeIndent s0.indent s1) - - Bad p x () -> - Bad p x () - ) - - -changeIndent : Int -> State -> State -changeIndent newIndent s = - { src = s.src - , offset = s.offset - , indent = newIndent - , row = s.row - , col = s.col - } - - -mapWithStartPosition : - (Location -> a -> b) - -> Parser x a - -> Parser x b -mapWithStartPosition combineStartAndResult (Parser parse) = - Parser - (\s0 -> - case parse s0 of - Good p a s1 -> - Good p (combineStartAndResult { row = s0.row, column = s0.col } a) s1 - - Bad p x () -> - Bad p x () - ) - - -mapWithEndPosition : - (a -> Location -> b) - -> Parser x a - -> Parser x b -mapWithEndPosition combineStartAndResult (Parser parse) = - Parser - (\s0 -> - case parse s0 of - Good p a s1 -> - Good p (combineStartAndResult a { row = s1.row, column = s1.col }) s1 - - Bad p x () -> - Bad p x () - ) - - -mapWithStartAndEndPosition : - (Location -> a -> Location -> b) - -> Parser x a - -> Parser x b -mapWithStartAndEndPosition combineStartAndResult (Parser parse) = - Parser - (\s0 -> - case parse s0 of - Good p a s1 -> - Good p (combineStartAndResult { row = s0.row, column = s0.col } a { row = s1.row, column = s1.col }) s1 - - Bad p x () -> - Bad p x () - ) - - - --- LOW-LEVEL HELPERS - - -{-| When making a fast parser, you want to avoid allocation as much as -possible. That means you never want to mess with the source string, only -keep track of an offset into that string. - -You use `isSubString` like this: - - isSubString "let" offset row col "let x = 4 in x" - --==> ( newOffset, newRow, newCol ) - -You are looking for `"let"` at a given `offset`. On failure, the -`newOffset` is `-1`. On success, the `newOffset` is the new offset. With -our `"let"` example, it would be `offset + 3`. - -You also provide the current `row` and `col` which do not align with -`offset` in a clean way. For example, when you see a `\n` you are at -`row = row + 1` and `col = 1`. Furthermore, some UTF16 characters are -two words wide, so even if there are no newlines, `offset` and `col` -may not be equal. - --} -isSubString : String -> Int -> Int -> Int -> String -> ( Int, Int, Int ) -isSubString smallString offset row col bigString = - -- TODO currently assumes smallString does not contain line \n - -- TODO currently assumes smallString does not contain UTF-16 characters - let - smallStringLength : Int - smallStringLength = - String.length smallString - - offsetAfter : Int - offsetAfter = - offset + smallStringLength - in - if String.slice offset offsetAfter bigString == smallString ++ "" then - ( offsetAfter, row, col + smallStringLength ) - - else - ( -1, row, col ) - - -{-| Again, when parsing, you want to allocate as little as possible. -So this function lets you say: - - isSubChar isSpace offset "this is the source string" - --==> newOffset - -The `(Char -> Bool)` argument is called a predicate. -The `newOffset` value can be a few different things: - - - `-1` means that the predicate failed - - `-2` means the predicate succeeded with a `\n` - - otherwise you will get `offset + 1` or `offset + 2` - depending on whether the UTF16 character is one or two - words wide. - --} -isSubChar : (Char -> Bool) -> Int -> String -> Int -isSubChar predicate offset string = - -- https://github.com/elm/parser/blob/1.1.0/src/Elm/Kernel/Parser.js#L37 - let - actualChar : String - actualChar = - String.slice offset (offset + 1) string - in - if charStringIsUtf16HighSurrogate actualChar then - -- String.all iterates over code points (so here just one Char) - if String.all predicate (String.slice offset (offset + 2) string) then - case actualChar of - "" -> - -1 - - _ -> - offset + 2 - - else - -1 - - else if String.all predicate actualChar then - case actualChar of - "\n" -> - -2 - - "" -> - -1 - - _ -> - offset + 1 - - else - -1 - - -charStringIsUtf16HighSurrogate : String -> Bool -charStringIsUtf16HighSurrogate charString = - charString |> String.all (\c -> c |> Char.toCode |> Basics.toFloat |> Basics.isNaN) diff --git a/src/ParserFast/Extra.elm b/src/ParserFast/Extra.elm deleted file mode 100644 index 65144f67..00000000 --- a/src/ParserFast/Extra.elm +++ /dev/null @@ -1,23 +0,0 @@ -module ParserFast.Extra exposing (anyChar) - -import ParserFast - - -anyChar : ParserFast.Parser Char -anyChar = - ParserFast.chompIf (always True) - |> ParserFast.getChompedString - |> ParserFast.andThen - (\s -> - case String.toList s of - [] -> - problemAnyCharacter - - c :: _ -> - ParserFast.succeed c - ) - - -problemAnyCharacter : ParserFast.Parser a -problemAnyCharacter = - ParserFast.problem "expected any character" diff --git a/src/ParserWithComments.elm b/src/ParserWithComments.elm index 285794b7..3327cd10 100644 --- a/src/ParserWithComments.elm +++ b/src/ParserWithComments.elm @@ -3,14 +3,12 @@ module ParserWithComments exposing , WithComments , many , manyWithoutReverse - , sepBy1 , until , untilWithoutReverse ) import Elm.Syntax.Node exposing (Node) import ParserFast exposing (Parser) -import ParserFast.Advanced import Rope exposing (Rope) @@ -23,75 +21,37 @@ type alias Comments = until : Parser () -> Parser (WithComments a) -> Parser (WithComments (List a)) -until end p = - let - step : - ( Comments, List a ) - -> - ParserFast.Parser - (ParserFast.Advanced.Step - ( Comments, List a ) - (WithComments (List a)) - ) - step ( commentsSoFar, itemsSoFar ) = - ParserFast.oneOf2 - (ParserFast.map - (\() -> - ParserFast.Advanced.Done - { comments = commentsSoFar - , syntax = List.reverse itemsSoFar - } - ) - end - ) - (ParserFast.map - (\pResult -> - ParserFast.Advanced.Loop - ( commentsSoFar |> Rope.prependTo pResult.comments - , pResult.syntax :: itemsSoFar - ) - ) - p - ) - in - ParserFast.Advanced.loop listEmptyWithCommentsTuple step +until end element = + ParserFast.loopUntil + end + element + ( Rope.empty, [] ) + (\pResult ( commentsSoFar, itemsSoFar ) -> + ( commentsSoFar |> Rope.prependTo pResult.comments + , pResult.syntax :: itemsSoFar + ) + ) + (\( commentsSoFar, itemsSoFar ) -> + { comments = commentsSoFar + , syntax = List.reverse itemsSoFar + } + ) many : Parser (WithComments a) -> Parser (WithComments (List a)) many p = - let - step : - ( Comments, List a ) - -> - ParserFast.Parser - (ParserFast.Advanced.Step - ( Comments, List a ) - (WithComments (List a)) - ) - step ( commentsSoFar, itemsSoFar ) = - ParserFast.orSucceedLazy - (ParserFast.map - (\pResult -> - ParserFast.Advanced.Loop - ( commentsSoFar |> Rope.prependTo pResult.comments - , pResult.syntax :: itemsSoFar - ) - ) - p - ) - (\() -> - ParserFast.Advanced.Done - { comments = commentsSoFar - , syntax = List.reverse itemsSoFar - } - ) - in - ParserFast.Advanced.loop listEmptyWithCommentsTuple step - - -listEmptyWithCommentsTuple : ( Rope a, List b ) -listEmptyWithCommentsTuple = - ( Rope.empty, [] ) + ParserFast.loopWhileSucceeds p + ( Rope.empty, [] ) + (\pResult ( commentsSoFar, itemsSoFar ) -> + ( commentsSoFar |> Rope.prependTo pResult.comments + , pResult.syntax :: itemsSoFar + ) + ) + (\( commentsSoFar, itemsSoFar ) -> + { comments = commentsSoFar + , syntax = List.reverse itemsSoFar + } + ) {-| Same as [`until`](#until), except that it doesn't reverse the list. @@ -101,30 +61,21 @@ Mind you the comments will be reversed either way -} untilWithoutReverse : Parser () -> Parser (WithComments a) -> Parser (WithComments (List a)) -untilWithoutReverse end p = - let - withoutReverseStep : - WithComments (List a) - -> - ParserFast.Parser - (ParserFast.Advanced.Step - (WithComments (List a)) - (WithComments (List a)) - ) - withoutReverseStep soFar = - ParserFast.oneOf2 - (ParserFast.map (\() -> ParserFast.Advanced.Done soFar) end) - (ParserFast.map - (\pResult -> - ParserFast.Advanced.Loop - { comments = soFar.comments |> Rope.prependTo pResult.comments - , syntax = pResult.syntax :: soFar.syntax - } - ) - p - ) - in - ParserFast.Advanced.loop listEmptyWithComments withoutReverseStep +untilWithoutReverse end element = + ParserFast.loopUntil + end + element + ( Rope.empty, [] ) + (\pResult ( commentsSoFar, itemsSoFar ) -> + ( commentsSoFar |> Rope.prependTo pResult.comments + , pResult.syntax :: itemsSoFar + ) + ) + (\( commentsSoFar, itemsSoFar ) -> + { comments = commentsSoFar + , syntax = itemsSoFar + } + ) {-| Same as [`many`](#many), except that it doesn't reverse the list. @@ -135,43 +86,15 @@ Mind you the comments will be reversed either way -} manyWithoutReverse : Parser (WithComments a) -> Parser (WithComments (List a)) manyWithoutReverse p = - let - withoutReverseStep : - WithComments (List a) - -> - ParserFast.Parser - (ParserFast.Advanced.Step - (WithComments (List a)) - (WithComments (List a)) - ) - withoutReverseStep soFar = - ParserFast.orSucceed - (ParserFast.map - (\pResult -> - ParserFast.Advanced.Loop - { comments = soFar.comments |> Rope.prependTo pResult.comments - , syntax = pResult.syntax :: soFar.syntax - } - ) - p - ) - (ParserFast.Advanced.Done soFar) - in - ParserFast.Advanced.loop listEmptyWithComments withoutReverseStep - - -listEmptyWithComments : WithComments (List b) -listEmptyWithComments = - { comments = Rope.empty, syntax = [] } - - -sepBy1 : String -> Parser (WithComments a) -> Parser (WithComments (List a)) -sepBy1 sep p = - ParserFast.map2 - (\head tail -> - { comments = head.comments |> Rope.prependTo tail.comments - , syntax = head.syntax :: tail.syntax + ParserFast.loopWhileSucceeds p + ( Rope.empty, [] ) + (\pResult ( commentsSoFar, itemsSoFar ) -> + ( commentsSoFar |> Rope.prependTo pResult.comments + , pResult.syntax :: itemsSoFar + ) + ) + (\( commentsSoFar, itemsSoFar ) -> + { comments = commentsSoFar + , syntax = itemsSoFar } ) - p - (many (ParserFast.symbolFollowedBy sep p)) diff --git a/src/Rope.elm b/src/Rope.elm index 4802c855..91f89cb8 100644 --- a/src/Rope.elm +++ b/src/Rope.elm @@ -1,4 +1,4 @@ -module Rope exposing (Rope, RopeFilled(..), empty, filledPrependTo, one, prependTo, toList) +module Rope exposing (Rope, RopeFilled(..), empty, filledPrependTo, one, prependTo, prependToFilled, toList) {-| inspired by [miniBill/elm-rope](https://dark.elm.dmy.fr/packages/miniBill/elm-rope/latest/) -} @@ -33,6 +33,16 @@ filledPrependTo right leftLikelyFilled = Just (Branch2 leftLikelyFilled rightLikelyFilled) +prependToFilled : RopeFilled a -> Rope a -> Rope a +prependToFilled rightLikelyFilled left = + case left of + Nothing -> + Just rightLikelyFilled + + Just leftLikelyFilled -> + Just (Branch2 leftLikelyFilled rightLikelyFilled) + + prependTo : Rope a -> Rope a -> Rope a prependTo right left = case left of diff --git a/tests/Elm/Parser/CommentTest.elm b/tests/Elm/Parser/CommentTest.elm index 27dfece3..aa0d2827 100644 --- a/tests/Elm/Parser/CommentTest.elm +++ b/tests/Elm/Parser/CommentTest.elm @@ -1,7 +1,6 @@ module Elm.Parser.CommentTest exposing (all) import Elm.Parser.Comments as Parser -import Elm.Parser.Node as Node import Elm.Parser.ParserWithCommentsTestUtil exposing (..) import Elm.Syntax.Node exposing (Node(..)) import Expect @@ -24,6 +23,11 @@ all = parseSingleLineComment "--bar" |> Expect.equal (Ok (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 6 } } "--bar")) + , test "singleLineComment including 2-part utf-16 char range" <| + \() -> + parseSingleLineComment "--bar🔧" + |> Expect.equal + (Ok (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 7 } } "--bar🔧")) , test "singleLineComment does not include new line" <| \() -> parseSingleLineComment "--bar\n" @@ -38,6 +42,11 @@ all = parseMultiLineComment "{-foo\nbar-}" |> Expect.equal (Ok (Node { start = { row = 1, column = 1 }, end = { row = 2, column = 6 } } "{-foo\nbar-}")) + , test "multilineComment including 2-part utf-16 char range" <| + \() -> + parseMultiLineComment "{-foo\nbar🔧-}" + |> Expect.equal + (Ok (Node { start = { row = 1, column = 1 }, end = { row = 2, column = 7 } } "{-foo\nbar🔧-}")) , test "nested multilineComment only open" <| \() -> parseMultiLineComment "{- {- -}" @@ -69,8 +78,9 @@ all = parseSingleLineComment : String -> Result (List Parser.DeadEnd) (Node String) parseSingleLineComment source = ParserFast.run - ((Parser.singleLineCommentCore |> ParserFast.getChompedString |> Node.parserCore) - |> ParserFast.ignore ParserFast.end + (ParserFast.map2 (\res () -> res) + Parser.singleLineComment + ParserFast.end ) source @@ -78,7 +88,8 @@ parseSingleLineComment source = parseMultiLineComment : String -> Result (List Parser.DeadEnd) (Node String) parseMultiLineComment source = ParserFast.run - ((Parser.multilineCommentString |> Node.parserCore) - |> ParserFast.ignore ParserFast.end + (ParserFast.map2 (\res () -> res) + Parser.multilineComment + ParserFast.end ) source diff --git a/tests/Elm/Parser/DeclarationsTests.elm b/tests/Elm/Parser/DeclarationsTests.elm index 7b7a88aa..1d1871b9 100644 --- a/tests/Elm/Parser/DeclarationsTests.elm +++ b/tests/Elm/Parser/DeclarationsTests.elm @@ -835,7 +835,7 @@ type Color = Blue String | Red | Green""" expectAst : Node Declaration -> String -> Expect.Expectation expectAst = - ParserWithCommentsUtil.expectAst declaration + ParserWithCommentsUtil.expectAstWithIndent1 declaration expectAstWithComments : { ast : Node Declaration, comments : List (Node String) } -> String -> Expect.Expectation diff --git a/tests/Elm/Parser/ExposeTests.elm b/tests/Elm/Parser/ExposeTests.elm index b3d9925d..cf078af7 100644 --- a/tests/Elm/Parser/ExposeTests.elm +++ b/tests/Elm/Parser/ExposeTests.elm @@ -3,8 +3,9 @@ module Elm.Parser.ExposeTests exposing (all) import Elm.Parser.Expose exposing (exposeDefinition) import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..)) -import Elm.Syntax.Node exposing (Node(..)) +import Elm.Syntax.Node as Node exposing (Node(..)) import Expect +import ParserFast import Test exposing (Test, describe, test) @@ -129,12 +130,18 @@ all = expectAst : Exposing -> String -> Expect.Expectation expectAst = - ParserWithCommentsUtil.expectAst exposeDefinition + ParserWithCommentsUtil.expectAst + (ParserFast.map (\expose -> { comments = expose.comments, syntax = Node.value expose.syntax }) + exposeDefinition + ) expectAstWithComments : { ast : Exposing, comments : List (Node String) } -> String -> Expect.Expectation expectAstWithComments = - ParserWithCommentsUtil.expectAstWithComments exposeDefinition + ParserWithCommentsUtil.expectAstWithComments + (ParserFast.map (\expose -> { comments = expose.comments, syntax = Node.value expose.syntax }) + exposeDefinition + ) expectInvalid : String -> Expect.Expectation diff --git a/tests/Elm/Parser/FileTests.elm b/tests/Elm/Parser/FileTests.elm index ad92b15c..8dcacb8a 100644 --- a/tests/Elm/Parser/FileTests.elm +++ b/tests/Elm/Parser/FileTests.elm @@ -468,4 +468,17 @@ port sendResponse : String -> Cmd msg , comments = [ Node { start = { row = 6, column = 1 }, end = { row = 7, column = 3 } } "{-| foo\n-}" ] } ) + , test "A file with a large number of comments should not create a stack overflow" <| + \() -> + let + comments : String + comments = + String.repeat 3000 "-- a\n" + in + ("""module Foo exposing (..) +a = 1 +""" ++ comments) + |> Elm.Parser.parseToFile + |> Result.map (\ast -> List.length ast.comments) + |> Expect.equal (Ok 3000) ] diff --git a/tests/Elm/Parser/ModuleTests.elm b/tests/Elm/Parser/ModuleTests.elm index b4513b22..eb205b9b 100644 --- a/tests/Elm/Parser/ModuleTests.elm +++ b/tests/Elm/Parser/ModuleTests.elm @@ -8,10 +8,11 @@ import Elm.Syntax.Exposing exposing (..) import Elm.Syntax.Expression exposing (Expression(..)) import Elm.Syntax.Infix exposing (InfixDirection(..)) import Elm.Syntax.Module exposing (..) -import Elm.Syntax.Node exposing (Node(..)) +import Elm.Syntax.Node as Node exposing (Node(..)) import Elm.Syntax.Pattern exposing (Pattern(..)) import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation(..)) import Expect +import Parser import ParserFast import Test exposing (..) @@ -126,7 +127,7 @@ b = 3 """) File.file |> Expect.equal - (Just + (Ok { moduleDefinition = Node { start = { row = 1, column = 1 }, end = { row = 1, column = 32 } } (NormalModule @@ -215,7 +216,7 @@ b = 3 """) File.file |> Expect.equal - (Just + (Ok { moduleDefinition = Node { start = { row = 1, column = 1 }, end = { row = 1, column = 32 } } (NormalModule @@ -286,7 +287,7 @@ a = 1 """ File.file |> Expect.equal - (Just + (Ok { moduleDefinition = Node { start = { row = 1, column = 1 }, end = { row = 1, column = 32 } } (NormalModule @@ -337,7 +338,7 @@ b = 2 """ File.file |> Expect.equal - (Just + (Ok { moduleDefinition = Node { start = { row = 1, column = 1 }, end = { row = 1, column = 32 } } (NormalModule @@ -436,7 +437,7 @@ fun2 n = """ File.file |> Expect.equal - (Just + (Ok { moduleDefinition = Node { start = { row = 1, column = 1 }, end = { row = 1, column = 31 } } (NormalModule { moduleName = Node { start = { row = 1, column = 8 }, end = { row = 1, column = 9 } } [ "A" ], exposingList = Node { start = { row = 1, column = 10 }, end = { row = 1, column = 31 } } (Explicit [ Node { start = { row = 1, column = 20 }, end = { row = 1, column = 24 } } (FunctionExpose "fun1"), Node { start = { row = 1, column = 26 }, end = { row = 1, column = 30 } } (FunctionExpose "fun2") ]) }) , imports = [] , declarations = @@ -497,17 +498,12 @@ fun2 n = expectAst : Module -> String -> Expect.Expectation expectAst = - ParserWithCommentsUtil.expectAst Parser.moduleDefinition + ParserWithCommentsUtil.expectAst (ParserFast.map (\mod -> { comments = mod.comments, syntax = Node.value mod.syntax }) Parser.moduleDefinition) -parseCore : String -> ParserFast.Parser a -> Maybe a +parseCore : String -> ParserFast.Parser a -> Result (List Parser.DeadEnd) a parseCore source parser = - case ParserFast.run (parser |> ParserFast.ignore ParserFast.end) source of - Err _ -> - Nothing - - Ok parsed -> - parsed |> Just + ParserFast.run (ParserFast.map2 (\res () -> res) parser ParserFast.end) source expectInvalid : String -> Expect.Expectation diff --git a/tests/Elm/Parser/NumbersTests.elm b/tests/Elm/Parser/NumbersTests.elm index f9c40d9c..18c941d8 100644 --- a/tests/Elm/Parser/NumbersTests.elm +++ b/tests/Elm/Parser/NumbersTests.elm @@ -1,8 +1,8 @@ module Elm.Parser.NumbersTests exposing (all) -import Elm.Parser.Numbers as Parser import Elm.Parser.TestUtil exposing (..) import Expect +import ParserFast import Test exposing (..) @@ -11,12 +11,12 @@ all = describe "NumbersTests" [ test "hex" <| \() -> - parse "0x03FFFFFF" (Parser.intOrHex (always Nothing) Just) + parse "0x03FFFFFF" (ParserFast.intOrHexMapWithRange (\_ _ -> Nothing) (\n _ -> Just n)) |> Expect.equal (Just (Just 67108863)) , test "hex - 2" <| \() -> - parse "0xFF" (Parser.intOrHex (always Nothing) Just) + parse "0xFF" (ParserFast.intOrHexMapWithRange (\_ _ -> Nothing) (\n _ -> Just n)) |> Expect.equal (Just (Just 255)) ] diff --git a/tests/Elm/Parser/ParserWithCommentsTestUtil.elm b/tests/Elm/Parser/ParserWithCommentsTestUtil.elm index 29f699fa..c4a5b8d6 100644 --- a/tests/Elm/Parser/ParserWithCommentsTestUtil.elm +++ b/tests/Elm/Parser/ParserWithCommentsTestUtil.elm @@ -1,4 +1,4 @@ -module Elm.Parser.ParserWithCommentsTestUtil exposing (expectAst, expectAstWithComments, expectInvalid, parse, parseWithState) +module Elm.Parser.ParserWithCommentsTestUtil exposing (expectAst, expectAstWithComments, expectAstWithIndent1, expectInvalid, parse, parseIndented0, parseWithState) import Elm.Syntax.Node exposing (Node) import Expect @@ -10,7 +10,7 @@ import Rope parseWithState : String -> ParserFast.Parser (WithComments a) -> Maybe { comments : List (Node String), syntax : a } parseWithState s p = - case ParserFast.run (p |> ParserFast.ignore ParserFast.end) s of + case ParserFast.run (ParserFast.map2 (\res () -> res) p ParserFast.end) s of Err _ -> Nothing @@ -27,9 +27,19 @@ parse s p = |> Maybe.map .syntax +parseIndented0 : String -> ParserFast.Parser (WithComments a) -> Maybe a +parseIndented0 s p = + case ParserFast.run (ParserFast.withIndent 0 (ParserFast.map2 (\res () -> res) p ParserFast.end)) s of + Err _ -> + Nothing + + Ok commentsAndSyntax -> + commentsAndSyntax.syntax |> Just + + parseWithFailure : String -> ParserFast.Parser (WithComments a) -> Result (List Parser.DeadEnd) a parseWithFailure s p = - case ParserFast.run (p |> ParserFast.ignore ParserFast.end) s of + case ParserFast.run (ParserFast.map2 (\res () -> res) p ParserFast.end) s of Err deadEnds -> Err deadEnds @@ -37,10 +47,29 @@ parseWithFailure s p = commentsAndSyntax.syntax |> Ok +expectAstWithIndent1 : ParserFast.Parser (WithComments a) -> a -> String -> Expect.Expectation +expectAstWithIndent1 parser = + \expected source -> + case ParserFast.run (ParserFast.map2 (\res () -> res) parser ParserFast.end) source of + Err error -> + Expect.fail ("Expected the source to be parsed correctly:\n" ++ Debug.toString error) + + Ok actual -> + Expect.all + [ \() -> actual.syntax |> Expect.equal expected + , \() -> + actual.comments + |> Rope.toList + |> Expect.equalLists [] + |> Expect.onFail "This parser should not produce any comments. If this is expected, then you should use expectAstWithComments instead." + ] + () + + expectAst : ParserFast.Parser (WithComments a) -> a -> String -> Expect.Expectation expectAst parser = \expected source -> - case ParserFast.run (parser |> ParserFast.ignore ParserFast.end) source of + case ParserFast.run (ParserFast.withIndent 0 (ParserFast.map2 (\res () -> res) parser ParserFast.end)) source of Err error -> Expect.fail ("Expected the source to be parsed correctly:\n" ++ Debug.toString error) @@ -59,7 +88,7 @@ expectAst parser = expectAstWithComments : ParserFast.Parser (WithComments a) -> { ast : a, comments : List (Node String) } -> String -> Expect.Expectation expectAstWithComments parser = \expected source -> - case ParserFast.run (parser |> ParserFast.ignore ParserFast.end) source of + case ParserFast.run (ParserFast.withIndent 0 (ParserFast.map2 (\res () -> res) parser ParserFast.end)) source of Err error -> Expect.fail ("Expected the source to be parsed correctly:\n" ++ Debug.toString error) diff --git a/tests/Elm/Parser/Samples.elm b/tests/Elm/Parser/Samples.elm index d58bf598..60052b86 100644 --- a/tests/Elm/Parser/Samples.elm +++ b/tests/Elm/Parser/Samples.elm @@ -54,6 +54,7 @@ allSamples = , ( 51, sample51 ) , ( 52, sample52 ) , ( 53, sample53 ) + , ( 54, sample54 ) ] @@ -1021,3 +1022,440 @@ rule (Configuration config) = |> Rule.fromProjectRuleSchema """ + + +sample54 : String +sample54 = + -- copied from https://github.com/pdamoc/elm-syntax-sscce + -- big thanks! + """port module Main exposing (Msg(..), Natural, main) + +{-| The above declaration shows how to: + + - declare a port module + - expose all the tags of a custom type (Msg) + - expose only the type (Natural) + +-} + +-- Shows how to import everthing from a module (Html). It is recommended to avoid this syntax. +-- Shows how to create an alias for a module name (Events) +-- Shows how to import multiple modules into a single namespace (Math). Use this with great care as it can create confusion about the source of a function. + +import Browser +import Html exposing (..) +import Html.Events as Events exposing (onClick) +import Math.Matrix4 as Math +import Math.Vector2 as Math +import Math.Vector3 as Math +import WebGL + + + +-- CUSTOM TYPES + + +{-| Shows how to define a single variant custom type. +Exposing only the type and not the tags ensures that the values of the type are created only through functions that can enforce constrains. +-} +type Natural + = Natural Int + + +{-| Shows how to define a function that creates a value for the above custom type +-} +fromInt : Int -> Natural +fromInt intValue = + -- Tags of the custom types are also acting as functions that create the custom type. + -- max function is defined in the Basics module from elm/core and is imported by default. See elm/core for a list of default imports. + Natural (max intValue 0) + + +{-| Shows how to unpack custom type parameters. Works only if the type has a single variant. +-} +toInt : Natural -> Int +toInt (Natural value) = + value + + +{-| Shows how to define a function in a pointfree style by composing two functions. +-} +toString : Natural -> String +toString = + -- String.fromInt shows how to use a module that is imported by default. + toInt >> String.fromInt + + +{-| Shows how to control the operations on the your custom type. +In this case the code makes sure you are not storing negative values inside the custom type +-} +addInt : Natural -> Int -> Natural +addInt natural intValue = + let + -- One ca unpack / destructure a custom type inside a let..in too + (Natural value) = + natural + in + fromInt (value + intValue) + + +{-| Adds 42 (the value is an Int written with HEX notation). +-} +addMeaning : Natural -> Natural +addMeaning (Natural value) = + fromInt (value + 0x2A) + + +{-| Shows how to create a type alias for a type that extends records. This alias will extend any other record with the field `name`. +-} +type alias Named a = + { a | name : String } + + +{-| Shows how to use the above type alias. +-} +type alias NamedValue a = + Named { value : a } + + +{-| Shows how to use the values from an extensible record alias fields +-} +namedToHtml : Named a -> Html msg +namedToHtml { name } = + text name + + +namedNaturalToHtml : NamedValue Natural -> Html msg +namedNaturalToHtml namedValue = + div [] + [ namedToHtml namedValue + , text ": " + , text (toString namedValue.value) + ] + + +{-| Shows how to create a phantom type +-} +type Unit a + = Unit Int + + +{-| When adding two units, the type parameter must be the same. +-} +addUnit : Unit a -> Unit a -> Unit a +addUnit (Unit first) (Unit second) = + Unit (first + second) + + +{-| A type to be used with the above Unit type +-} +type Meter + = Meter + + +{-| A second type to be used with the above Unit type +-} +type Gram + = Gram + + +twoMeters : Unit Meter +twoMeters = + Unit 2 + + +threeMeters : Unit Meter +threeMeters = + Unit 3 + + +fewGrams : Unit Gram +fewGrams = + Unit 21 + + +someMeters : Unit Meter +someMeters = + -- This works because the two units match + addUnit twoMeters threeMeters + + + +{- This value will throw an error if uncommented + impossibleAdd : Unit Meter + impossibleAdd = + -- This doesn't work because the types don't match + addUnit fewGrams someMeters +-} +-- MODEL + + +{-| Shows how to tie a name to a record type. +-} +type alias Model = + { count : Natural + , namedCount : NamedValue Natural + } + + +{-| Shows how to ignore a parameter you are not using +This purposefully shows a function without a type signature although top level functions and values should have type signatures. +-} +init _ = + ( { count = Natural 0 + , namedCount = + { name = "Natural", value = Natural 0 } + } + , Cmd.none + ) + + + +-- UPDATE + + +{-| Shows how to give a new name to a more complex type +-} +type alias Naturals = + List Natural + + +{-| Shows how to define a custom type with multiple variants +-} +type Msg + = Increment + | Decrement + | AddNextTen + | OnSubscription (Result String Naturals) + + +{-| Shows how to unpack a record parameter while still keeping the full parameter. +You can use a subset of the fields in the record if you only need certain fields. +This function type signature has been purpusefully spread over multiple lines to show that complex signatures need not be single line. +-} +update : + Msg + -> Model + -> ( Model, Cmd msg ) +update msg ({ count } as model) = + case msg of + Increment -> + ( { model | count = addInt count 1 }, Cmd.none ) + + Decrement -> + -- Shows how to create a new scope with a let..in expression + let + -- values and function defined inside let..in can have type signatures although they usually don't + newValue : Natural + newValue = + addInt count -1 + + -- this is how to use the Debug.log to check for a value + _ = + Debug.log "newValue" newValue + + -- this shows that you can declare multiple _ values without the compiler complaining. + -- attempting to use a named declaration multiple times will result in a compiler error + _ = + newValue + -- adding the next line at the end of a declaration with result in it being logged to the JS console + |> Debug.log "newValue" + in + if newValue == count then + -- Shows how to call a port + ( model, reportError "There are no negative Natural numbers" ) + + else + ( { model | count = newValue }, Cmd.none ) + + AddNextTen -> + let + addIntToCount = + -- Shows how to partially apply a function. + -- This is very useful in scopes where the first arguments stay the same + addInt count + + intCount = + toInt count + + addTen = + -- Shows how to use an infix operator as a prefix function. + -- This is useful when you want to partially apply the operator and use + -- the resulting function in a higher order function. + (+) 10 + + nextTen = + -- Shows how to use an infix operator as an argument in a higher order function. + List.foldl (+) 0 (List.range intCount (addTen intCount)) + in + ( { model | count = addIntToCount nextTen }, Cmd.none ) + + -- Shows how to unpack a variant by matching against the contained variants + OnSubscription (Ok naturals) -> + case naturals of + -- Shows how to pattern match on a List + [] -> + -- Shows how to recursively call update in order to avoid duplicating code. + update (OnSubscription (Err "Received an empty list")) model + + -- Shows how to pattern match on a list with a fixed number of elements + [ first ] -> + ( { model | count = first }, Cmd.none ) + + -- Shows how to pattern match on a list with at least two elements. + first :: second :: _ -> + ( { model | count = first }, Cmd.none ) + + OnSubscription (Err error) -> + ( model, reportError error ) + + + +-- VIEW + + +{-| Shows how to declare a String that spans multiple lines +-} +multiline : String +multiline = + \"\"\" + This is a multiline string. + It will be displayed by spliting the lines into separate paragraphs. +\"\"\" + + +{-| Shows how to define a tuple. +-} +initials : ( Char, Char ) +initials = + -- Show how to declare a Char type. + ( 'J', 'D' ) + + +view : Model -> Html Msg +view model = + let + namedCount = + -- Shows how to access a field from a record by using a field accessor function + .namedCount model + + -- Shows how to pattern match a tuple + ( first, last ) = + initials + + -- a helper function + named value = + { name = value } + + -- shows that record field accessors work on expressions too + nameFromExpression = + (named "Foo").name + in + main_ [] + [ button [ onClick Increment ] [ text "+1" ] + + -- Shows how to avoid parentheses by using the backwards pipe operator + , div [] [ text <| toString model.count ] + + -- Shows how to used a function from a module without having to expose it in the import section. + , div [] [ button [ Events.onClick Decrement ] [ text "-1" ] ] + , button [ onClick AddNextTen ] [ text "Add Next Ten" ] + , div [] [ namedNaturalToHtml namedCount ] + , String.lines multiline + |> List.map (\\line -> p [] [ text line ]) + |> div [] + , footer [] [ text (String.fromChar first), text (String.fromChar last) ] + ] + + + +-- SUBSCRIPTIONS + + +subscriptions : Model -> Sub Msg +subscriptions model = + -- Listen to the incomming port only if condition in the model is met + if toInt model.count < 5 then + -- Show how to use an anonymous function (lambda expression) + fromJS + (\\value -> + case List.map String.toInt value of + [] -> + OnSubscription (Err "Received an empty list") + + (Just int) :: _ -> + let + output = + -- This shows how to prepend an element to a list + fromInt int :: [] + in + if int >= 0 then + OnSubscription (Ok output) + + else + -- Shows how to structure a complex function application by using the "pipe" operator + ("Received a negative number: " ++ String.fromInt int) + |> Err + |> OnSubscription + + -- Shows how to catch all remaining variants. Watch out for this pattern as it can create troubles. + _ -> + OnSubscription (Err "Received a list that started with a non-integer ") + ) + + else + Sub.none + + + +-- WIRING + + +{-| Signature for Browser.element is Program flags model msg. +The flags type argument here is the Unit type: () +-} +main : Program () Model Msg +main = + Browser.element + { init = init + , view = view + , update = update + , subscriptions = subscriptions + } + + + +-- PORTS + + +{-| Shows how to define an outgoing port +-} +port reportError : String -> Cmd msg + + +{-| Shows how to define an incomming port. +The first parameter is a function that takes the data received from JS and produces a message that the app understands. +-} +port fromJS : (List String -> msg) -> Sub msg + + + +-- ADVANCED SYNTAX + + +{-| Elm also has special syntax for declaring WebGL shaders. See more about this at: +-} +vertexShader : WebGL.Shader { a | coord : Math.Vec3, position : Math.Vec3 } { b | view : Math.Mat4 } { vcoord : Math.Vec2 } +vertexShader = + [glsl| + +attribute vec3 position; +attribute vec3 coord; +uniform mat4 view; +varying vec2 vcoord; + +void main () { + gl_Position = view * vec4(position, 1.0); + vcoord = coord.xy; +} +|]""" diff --git a/tests/Elm/Parser/TestUtil.elm b/tests/Elm/Parser/TestUtil.elm index 2db1fb45..24cf7c74 100644 --- a/tests/Elm/Parser/TestUtil.elm +++ b/tests/Elm/Parser/TestUtil.elm @@ -11,4 +11,4 @@ parse source p = parseToResult : String -> ParserFast.Parser a -> Result (List Parser.DeadEnd) a parseToResult source p = - ParserFast.run (p |> ParserFast.ignore ParserFast.end) source + ParserFast.run (ParserFast.map2 (\res () -> res) p ParserFast.end) source diff --git a/tests/Elm/Parser/TokenTests.elm b/tests/Elm/Parser/TokenTests.elm index fbd70c37..581d2dca 100644 --- a/tests/Elm/Parser/TokenTests.elm +++ b/tests/Elm/Parser/TokenTests.elm @@ -81,47 +81,47 @@ all = |> Expect.equal Nothing , test "multiline string" <| \() -> - parse "\"\"\"Bar foo \n a\"\"\"" Parser.singleOrTripleQuotedStringLiteral + parse "\"\"\"Bar foo \n a\"\"\"" (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) |> Expect.equal (Just "Bar foo \n a") , test "multiline string escape" <| \() -> - parse """\"\"\" \\\"\"\" \"\"\"""" Parser.singleOrTripleQuotedStringLiteral + parse """\"\"\" \\\"\"\" \"\"\"""" (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) |> Expect.equal (Just """ \"\"\" """) , test "character escaped" <| \() -> - parse "'\\''" Parser.characterLiteral + parse "'\\''" (Parser.characterLiteralMapWithRange (\_ c -> c)) |> Expect.equal (Just '\'') , test "character escaped - 2" <| \() -> - parse "'\\r'" Parser.characterLiteral + parse "'\\r'" (Parser.characterLiteralMapWithRange (\_ c -> c)) |> Expect.equal (Just '\u{000D}') , test "unicode char" <| \() -> - parse "'\\u{000D}'" Parser.characterLiteral + parse "'\\u{000D}'" (Parser.characterLiteralMapWithRange (\_ c -> c)) |> Expect.equal (Just '\u{000D}') , test "unicode char with lowercase hex" <| \() -> - parse "'\\u{000d}'" Parser.characterLiteral + parse "'\\u{000d}'" (Parser.characterLiteralMapWithRange (\_ c -> c)) |> Expect.equal (Just '\u{000D}') , test "string escaped 3" <| \() -> - parse "\"\\\"\"" Parser.singleOrTripleQuotedStringLiteral + parse "\"\\\"\"" (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) |> Expect.equal (Just "\"") , test "string escaped" <| \() -> - parse "\"foo\\\\\"" Parser.singleOrTripleQuotedStringLiteral + parse "\"foo\\\\\"" (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) |> Expect.equal (Just "foo\\") , test "character escaped 3" <| \() -> - parse "'\\n'" Parser.characterLiteral + parse "'\\n'" (Parser.characterLiteralMapWithRange (\_ c -> c)) |> Expect.equal (Just '\n') , test "long string" <| \() -> - parse longString Parser.singleOrTripleQuotedStringLiteral + parse longString (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) |> Expect.notEqual Nothing , test "long multi line string" <| \() -> - parse longMultiLineString Parser.singleOrTripleQuotedStringLiteral + parse longMultiLineString (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) |> Expect.notEqual Nothing , test "ρ function" <| \() -> diff --git a/tests/Elm/Parser/TypeAnnotationTests.elm b/tests/Elm/Parser/TypeAnnotationTests.elm index b62dac58..f80a3b72 100644 --- a/tests/Elm/Parser/TypeAnnotationTests.elm +++ b/tests/Elm/Parser/TypeAnnotationTests.elm @@ -1,12 +1,10 @@ module Elm.Parser.TypeAnnotationTests exposing (all) -import Elm.Parser.Layout as Layout -import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil exposing (..) +import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil import Elm.Parser.TypeAnnotation as Parser import Elm.Syntax.Node exposing (Node(..)) import Elm.Syntax.TypeAnnotation exposing (..) import Expect -import ParserFast import Test exposing (..) @@ -68,10 +66,6 @@ all = (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 4 } } (Typed (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 4 } } ( [], "Bar" )) []) ) - , test "types with and without spacing should parse to the same" <| - \() -> - parse "Bar " (Parser.typeAnnotation |> ParserFast.ignore Layout.maybeLayout) - |> Expect.equal (parse "Bar" Parser.typeAnnotation) , test "typedTypeReference 1" <| \() -> "Foo () a Bar" diff --git a/tests/Elm/WriterTests.elm b/tests/Elm/WriterTests.elm index 6259b9de..551838a6 100644 --- a/tests/Elm/WriterTests.elm +++ b/tests/Elm/WriterTests.elm @@ -1,7 +1,7 @@ module Elm.WriterTests exposing (suite) import Elm.Parser.Expression exposing (expression) -import Elm.Parser.ParserWithCommentsTestUtil exposing (parse) +import Elm.Parser.ParserWithCommentsTestUtil exposing (parseIndented0) import Elm.Syntax.Declaration exposing (..) import Elm.Syntax.Exposing exposing (..) import Elm.Syntax.Expression exposing (..) @@ -75,7 +75,7 @@ suite = input = "(.spaceEvenly Internal.Style.classes)" in - parse input expression + parseIndented0 input expression |> Maybe.map Writer.writeExpression |> Maybe.map Writer.write |> Expect.equal