diff --git a/finished/elm-package.json b/finished/elm-package.json index bc1d227..7c979f1 100644 --- a/finished/elm-package.json +++ b/finished/elm-package.json @@ -15,7 +15,6 @@ "elm-lang/html": "2.0.0 <= v < 3.0.0", "elm-lang/http": "1.0.0 <= v < 2.0.0", "elm-lang/navigation": "2.1.0 <= v < 3.0.0", - "elm-tools/parser": "2.0.1 <= v < 3.0.0", "evancz/elm-markdown": "3.0.2 <= v < 4.0.0", "evancz/url-parser": "2.0.1 <= v < 3.0.0", "lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0", diff --git a/finished/elm-stuff/exact-dependencies.json b/finished/elm-stuff/exact-dependencies.json index 2e36c57..77b48b4 100644 --- a/finished/elm-stuff/exact-dependencies.json +++ b/finished/elm-stuff/exact-dependencies.json @@ -1,7 +1,6 @@ { "rtfeldman/elm-validate": "3.0.0", "rtfeldman/selectlist": "1.0.0", - "elm-tools/parser-primitives": "1.0.0", "elm-lang/navigation": "2.1.0", "elm-lang/virtual-dom": "2.0.4", "evancz/url-parser": "2.0.1", @@ -9,7 +8,6 @@ "evancz/elm-markdown": "3.0.2", "elm-lang/dom": "1.1.1", "elm-lang/html": "2.0.0", - "elm-tools/parser": "2.0.1", "elm-community/json-extra": "2.7.0", "elm-lang/http": "1.0.0", "lukewestby/elm-http-builder": "5.2.0", diff --git a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/.gitignore b/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/.gitignore deleted file mode 100644 index e185314..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/.gitignore +++ /dev/null @@ -1 +0,0 @@ -elm-stuff \ No newline at end of file diff --git a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/LICENSE b/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/LICENSE deleted file mode 100644 index 81f65b3..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2017-present, Evan Czaplicki -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* 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. - -* Neither the name of the {organization} 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. diff --git a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/README.md b/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/README.md deleted file mode 100644 index 647a900..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Parser Primitives - -**In 99.9999% of cases, you do not want this.** - -When creating a parser combinator library like [`elm-tools/parser`](https://github.com/elm-tools/parser), you want lower-level access to strings to get better performance. - -This package exposes these low-level functions so that `elm-tools/parser` does not have an unfair performance advantage. diff --git a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/elm-package.json b/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/elm-package.json deleted file mode 100644 index c673a2d..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/elm-package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "version": "1.0.0", - "summary": "Fast (but safe) primitives for creating parsing packages", - "repository": "https://github.com/elm-tools/parser-primitives.git", - "license": "BSD-3-Clause", - "source-directories": [ - "src" - ], - "exposed-modules": [ - "ParserPrimitives" - ], - "dependencies": { - "elm-lang/core": "5.0.0 <= v < 6.0.0" - }, - "native-modules": true, - "elm-version": "0.18.0 <= v < 0.19.0" -} diff --git a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/src/Native/ParserPrimitives.js b/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/src/Native/ParserPrimitives.js deleted file mode 100644 index c7a6578..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/src/Native/ParserPrimitives.js +++ /dev/null @@ -1,130 +0,0 @@ -var _elm_tools$parser_primitives$Native_ParserPrimitives = function() { - - -// STRINGS - -function isSubString(smallString, offset, row, col, bigString) -{ - var smallLength = smallString.length; - var bigLength = bigString.length - offset; - - if (bigLength < smallLength) - { - return tuple3(-1, row, col); - } - - for (var i = 0; i < smallLength; i++) - { - var char = smallString[i]; - - if (char !== bigString[offset + i]) - { - return tuple3(-1, row, col); - } - - // if it is a two word character - if ((bigString.charCodeAt(offset) & 0xF800) === 0xD800) - { - i++ - if (smallString[i] !== bigString[offset + i]) - { - return tuple3(-1, row, col); - } - col++; - continue; - } - - // if it is a newline - if (char === '\n') - { - row++; - col = 1; - continue; - } - - // if it is a one word character - col++ - } - - return tuple3(offset + smallLength, row, col); -} - -function tuple3(a, b, c) -{ - return { ctor: '_Tuple3', _0: a, _1: b, _2: c }; -} - - -// CHARS - -var mkChar = _elm_lang$core$Native_Utils.chr; - -function isSubChar(predicate, offset, string) -{ - if (offset >= string.length) - { - return -1; - } - - if ((string.charCodeAt(offset) & 0xF800) === 0xD800) - { - return predicate(mkChar(string.substr(offset, 2))) - ? offset + 2 - : -1; - } - - var char = string[offset]; - - return predicate(mkChar(char)) - ? ((char === '\n') ? -2 : (offset + 1)) - : -1; -} - - -// FIND STRING - -function findSubString(before, smallString, offset, row, col, bigString) -{ - var newOffset = bigString.indexOf(smallString, offset); - - if (newOffset === -1) - { - return tuple3(-1, row, col); - } - - var scanTarget = before ? newOffset : newOffset + smallString.length; - - while (offset < scanTarget) - { - var char = bigString[offset]; - - if (char === '\n') - { - offset++; - row++; - col = 1; - continue; - } - - if ((bigString.charCodeAt(offset) & 0xF800) === 0xD800) - { - offset += 2; - col++; - continue; - } - - offset++; - col++; - } - - return tuple3(offset, row, col); -} - - -return { - isSubString: F5(isSubString), - isSubChar: F3(isSubChar), - findSubString: F6(findSubString) -}; - -}(); diff --git a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/src/ParserPrimitives.elm b/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/src/ParserPrimitives.elm deleted file mode 100644 index e93d647..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser-primitives/1.0.0/src/ParserPrimitives.elm +++ /dev/null @@ -1,109 +0,0 @@ -module ParserPrimitives exposing - ( isSubString - , isSubChar - , findSubString - ) - -{-| Low-level functions for creating parser combinator libraries. - -@docs isSubString, isSubChar, findSubString --} - -import Native.ParserPrimitives - - - --- STRINGS - - -{-| 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 = - Native.ParserPrimitives.isSubString - - - --- CHARACTERS - - -{-| 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. - -It is better to use union types in general, but it is worth the -danger *within* parsing libraries to get the benefit *outside*. - -So you can write a `chomp` function like this: - - chomp : (Char -> Bool) -> Int -> Int -> Int -> String -> (Int, Int, Int) - chomp isGood offset row col source = - let - newOffset = - Prim.isSubChar isGood offset source - in - -- no match - if newOffset == -1 then - (offset, row, col) - - -- newline match - else if newOffset == -2 then - chomp isGood (offset + 1) (row + 1) 1 source - - -- normal match - else - chomp isGood newOffset row (col + 1) source - -Notice that `chomp` can be tail-call optimized, so this turns into a -`while` loop under the hood. --} -isSubChar : (Char -> Bool) -> Int -> String -> Int -isSubChar = - Native.ParserPrimitives.isSubChar - - - --- INDEX - - -{-| Find a substring after a given offset. - - findSubString before "42" offset row col "Is 42 the answer?" - --==> (newOffset, newRow, newCol) - -If `offset = 0` and `before = True` we would get `(3, 1, 4)` -If `offset = 0` and `before = False` we would get `(5, 1, 6)` - -If `offset = 7` we would get `(-1, 1, 18)` --} -findSubString : Bool -> String -> Int -> Int -> Int -> String -> (Int, Int, Int) -findSubString = - Native.ParserPrimitives.findSubString diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/.gitignore b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/.gitignore deleted file mode 100644 index e185314..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/.gitignore +++ /dev/null @@ -1 +0,0 @@ -elm-stuff \ No newline at end of file diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/LICENSE b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/LICENSE deleted file mode 100644 index 81f65b3..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2017-present, Evan Czaplicki -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* 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. - -* Neither the name of the {organization} 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. diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/README.md b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/README.md deleted file mode 100644 index 50289ed..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/README.md +++ /dev/null @@ -1,170 +0,0 @@ -# Parser + Nice Error Messages - -Goals: - - - Make writing parsers as simple and fun as possible. - - Produce excellent error messages. - - Go pretty fast. - -This is achieved with a couple concepts that I have not seen in any other parser libraries: [parser pipelines](#parser-pipelines), [tracking context](#tracking-context), and [delayed commits](#delayed-commits). - - -## Parser Pipelines - -To parse a 2D point like `( 3, 4 )`, you might create a `point` parser like this: - -```elm -import Parser exposing (Parser, (|.), (|=), succeed, symbol, float, ignore, zeroOrMore) - - -type alias Point = - { x : Float - , y : Float - } - - -point : Parser Point -point = - succeed Point - |. symbol "(" - |. spaces - |= float - |. spaces - |. symbol "," - |. spaces - |= float - |. spaces - |. symbol ")" - - -spaces : Parser () -spaces = - ignore zeroOrMore (\c -> c == ' ') -``` - -All the interesting stuff is happening in `point`. It uses two operators: - - - [`(|.)`][ignore] means “parse this, but **ignore** the result” - - [`(|=)`][keep] means “parse this, and **keep** the result” - -So the `Point` function only gets the result of the two `float` parsers. - -[ignore]: http://package.elm-lang.org/packages/elm-tools/parser/latest/Parser#|. -[keep]: http://package.elm-lang.org/packages/elm-tools/parser/latest/Parser#|= - -The theory is that `|=` introduces more “visual noise” than `|.`, making it pretty easy to pick out which lines in the pipeline are important. - -I recommend having one line per operator in your parser pipeline. If you need multiple lines for some reason, use a `let` or make a helper function. - - -## Tracking Context - -Most parsers tell you the row and column of the problem: - - Something went wrong at (4:17) - -That may be true, but it is not how humans think. It is how text editors think! It would be better to say: - - I found a problem with this list: - - [ 1, 23zm5, 3 ] - ^ - I wanted an integer, like 6 or 90219. - -Notice that the error messages says `this list`. That is context! That is the language my brain speaks, not rows and columns. - -This parser package lets you annotate context with the [`inContext`][inContext] function. You can let the parser know “I am trying to parse a `"list"` right now” so if an error happens anywhere in that context, you get the hand annotation! - -[inContext]: http://package.elm-lang.org/packages/elm-tools/parser/latest/Parser#inContext - -> **Note:** This technique is used by the parser in the Elm compiler to give more helpful error messages. - - -## Delayed Commits - -To make fast parsers with precise error messages, this package lets you control when a parser **commits** to a certain path. - -For example, you are trying to parse the following list: - -```elm -[ 1, 23zm5, 3 ] -``` - -Ideally, you want the error at the `z`, but the libraries I have seen make this difficult to achieve efficiently. You often end up with an error at `[` because “something went wrong”. - -**This package introduces [`delayedCommit`][delayedCommit] to resolve this.** - -Say we want to create `intList`, a parser for comma separated lists of integers like `[1, 2, 3]`. We would say something like this: - -[delayedCommit]: http://package.elm-lang.org/packages/elm-tools/parser/latest/Parser#delayedCommit - -```elm -import Parser exposing (..) - - -{-| We start by ignoring the opening square brace and some spaces. -We only really care about the numbers, so we parse an `int` and -then use `intListHelp` to start chomping other list entries. --} -intList : Parser (List Int) -intList = - succeed identity - |. symbol "[" - |. spaces - |= andThen (\n -> intListHelp [n]) int - |. spaces - |. symbol "]" - - -{-| `intListHelp` checks if there is a `nextInt`. If so, it -continues trying to find more list items. If not, it gives -back the list of integers we have accumulated so far. --} -intListHelp : List Int -> Parser (List Int) -intListHelp revInts = - oneOf - [ nextInt - |> andThen (\n -> intListHelp (n :: revInts)) - , succeed (List.reverse revInts) - ] -``` - -Now we get to the tricky part! How do we define `nextInt`? Here are two approaches, but only the second one actually works! - - -```elm --- BAD -badNextInt : Parser Int -badNextInt = - succeed identity - |. spaces - |. symbol "," - |. spaces - |= int - --- GOOD -nextInt : Parser Int -nextInt = - delayedCommit spaces <| - succeed identity - |. symbol "," - |. spaces - |= int -``` - -The `badNextInt` looks pretty normal, but it will not work. It commits as soon as the first `spaces` parser succeeds. It fails in the following situation: - -```elm -[ 1, 2, 3 ] - ^ -``` - -When we get to the closing `]` we have already successfully parsed some spaces. That means we are commited to `badNextInt` and need a comma. That fails, so the whole parse fails! - -With `nextInt`, the [`delayedCommit`][delayedCommit] function is saying to parse `spaces` but only commit if progress is made *after* that. So we are only commited to this parser if we see a comma. - -
- -
- -## [Comparison with Prior Work](https://github.com/elm-tools/parser/blob/master/comparison.md) diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/comparison.md b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/comparison.md deleted file mode 100644 index 81ef545..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/comparison.md +++ /dev/null @@ -1,69 +0,0 @@ -## Comparison with Prior Work - -I have not seen the [parser pipeline][1] or the [context stack][2] ideas in other libraries, but [delayed commits][3] relate to prior work. - -[1]: README.md#parser-pipelines -[2]: README.md#tracking-context -[3]: README.md#delayed-commits - -Most parser combinator libraries I have seen are based on Haskell’s Parsec library, which has primitives named `try` and `lookAhead`. I believe [`delayedCommitMap`][delayedCommitMap] is a better primitive for two reasons. - -[delayedCommitMap]: http://package.elm-lang.org/packages/elm-tools/parser/latest/Parser#delayedCommitMap - - -### Performance and Composition - -Say we want to create a precise error message for `length [1,,3]`. The naive approach with Haskell’s Parsec library produces very bad error messages: - -```haskell -spaceThenArg :: Parser Expr -spaceThenArg = - try (spaces >> term) -``` - -This means we get a precise error from `term`, but then throw it away and say something went wrong at the space before the `[`. Very confusing! To improve quality, we must write something like this: - -```haskell -spaceThenArg :: Parser Expr -spaceThenArg = - choice - [ do lookAhead (spaces >> char '[') - spaces - term - , try (spaces >> term) - ] -``` - -Notice that we parse `spaces` twice no matter what. - -Notice that we also had to hardcode `[` in the `lookAhead`. What if we update `term` to parse records that start with `{` as well? To get good commits on records, we must remember to update `lookAhead` to look for `oneOf "[{"`. Implementation details are leaking out of `term`! - -With `delayedCommit` in this Elm library, you can just say: - -```elm -spaceThenArg : Parser Expr -spaceThenArg = - delayedCommit spaces term -``` - -It does less work, and is more reliable as `term` evolves. I believe `delayedCommit` makes `lookAhead` pointless. - - -### Expressiveness - -You can define `try` in terms of [`delayedCommitMap`][delayedCommitMap] like this: - -```elm -try : Parser a -> Parser a -try parser = - delayedCommitMap always parser (succeed ()) -``` - -No expressiveness is lost! - -While it is possible to define `try`, I left it out of this package. In practice, `try` often leads to “bad commits” where your parser fails in a very specific way, but you then backtrack to a less specific error message. I considered naming it `allOrNothing` to better explain how it changes commit behavior, but ultimately, I thought it was best to encourage users to express their parsers with `delayedCommit` directly. - - -### Summary - -Compared to previous work, `delayedCommit` lets you produce precise error messages **more efficiently**. By thinking about “commit behavior” directly, you also end up with **cleaner composition** of parsers. And these benefits come **without any loss of expressiveness**. diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/elm-package.json b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/elm-package.json deleted file mode 100644 index 7c82383..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/elm-package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "2.0.1", - "summary": "a parsing library, focused on simplicity and great error messages", - "repository": "https://github.com/elm-tools/parser.git", - "license": "BSD-3-Clause", - "source-directories": [ - "src" - ], - "exposed-modules": [ - "Parser", - "Parser.LanguageKit", - "Parser.LowLevel" - ], - "dependencies": { - "elm-lang/core": "5.1.0 <= v < 6.0.0", - "elm-tools/parser-primitives": "1.0.0 <= v < 2.0.0" - }, - "elm-version": "0.18.0 <= v < 0.19.0" -} diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser.elm b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser.elm deleted file mode 100644 index 163d26b..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser.elm +++ /dev/null @@ -1,1116 +0,0 @@ -module Parser exposing - ( Parser - , run - , int, float, symbol, keyword, end - , Count(..), zeroOrMore, oneOrMore, keep, ignore, repeat - , succeed, fail, map, oneOf, (|=), (|.), map2, lazy, andThen - , delayedCommit, delayedCommitMap - , source, sourceMap, ignoreUntil - , Error, Problem(..), Context, inContext - ) - -{-| - -# Parsers -@docs Parser, run - -# Numbers and Keywords -@docs int, float, symbol, keyword, end - -# Repeat Parsers -@docs Count, zeroOrMore, oneOrMore, keep, ignore, repeat - -# Combining Parsers -@docs succeed, fail, map, oneOf, (|=), (|.), map2, lazy, andThen - -# Delayed Commits -@docs delayedCommit, delayedCommitMap - -# Efficiency Tricks -@docs source, sourceMap, ignoreUntil - -# Errors -@docs Error, Problem, Context, inContext --} - -import Char -import Parser.Internal as Internal exposing (Parser(..), Step(..)) -import ParserPrimitives as Prim - - - --- PARSER - - -{-| A parser! If you have a `Parser Int`, it is a parser that turns -strings into integers. --} -type alias Parser a = - Internal.Parser Context Problem a - - -type alias Step a = - Internal.Step Context Problem a - - -type alias State = - Internal.State Context - - -{-| Actually run a parser. - - run (keyword "true") "true" == Ok () - run (keyword "true") "True" == Err ... - run (keyword "true") "false" == Err ... --} -run : Parser a -> String -> Result Error a -run (Parser parse) source = - let - initialState = - { source = source - , offset = 0 - , indent = 1 - , context = [] - , row = 1 - , col = 1 - } - in - case parse initialState of - Good a _ -> - Ok a - - Bad problem { row, col, context } -> - Err - { row = row - , col = col - , source = source - , problem = problem - , context = context - } - - --- ERRORS - - -{-| Parse errors as data. You can format it however makes the most -sense for your application. Maybe that is all text, or maybe it is fancy -interactive HTML. Up to you! - -You get: - - - The `row` and `col` of the error. - - The full `source` provided to the [`run`](#run) function. - - The actual `problem` you ran into. - - A stack of `context` that describes where the error is *conceptually*. - -**Note:** `context` is a stack. That means [`inContext`](#inContext) -adds to the *front* of this list, not the back. So if you want the -[`Context`](#Context) closest to the error, you want the first element -of the `context` stack. --} -type alias Error = - { row : Int - , col : Int - , source : String - , problem : Problem - , context : List Context - } - - -{-| The particular problem you ran into. - -The tricky one here is `BadRepeat`. That means that you are running -`zeroOrMore parser` where `parser` can succeed without consuming any -input. That means it will just loop forever, consuming no input until -the program crashes. --} -type Problem - = BadOneOf (List Problem) - | BadInt - | BadFloat - | BadRepeat - | ExpectingEnd - | ExpectingSymbol String - | ExpectingKeyword String - | ExpectingVariable - | ExpectingClosing String - | Fail String - - -{-| Most parsers only let you know the row and column where the error -occurred. But what if you could *also* say “the error occured **while -parsing a list**” and let folks know what the *parser* thinks it is -doing?! - -The error messages would be a lot nicer! That is what Elm compiler does, -and it is what `Context` helps you do in this library! **See the -[`inContext`](#inContext) docs for a nice example!** - -About the actual fields: - - - `description` is set by [`inContext`](#inContext) - - `row` and `col` are where [`inContext`](#inContext) began - -Say you use `inContext` in your list parser. And say get an error trying -to parse `[ 1, 23zm5, 3 ]`. In addition to error information about `23zm5`, -you would have `Context` with the row and column of the starting `[` symbol. --} -type alias Context = - { row : Int - , col : Int - , description : String - } - - - --- PRIMITIVES - - -{-| A parser that succeeds without consuming any text. - - run (succeed 90210 ) "mississippi" == Ok 90210 - run (succeed 3.141 ) "mississippi" == Ok 3.141 - run (succeed () ) "mississippi" == Ok () - run (succeed Nothing) "mississippi" == Ok Nothing - -Seems weird, but it is often useful in combination with -[`oneOf`](#oneOf) or [`andThen`](#andThen). --} -succeed : a -> Parser a -succeed a = - Parser <| \state -> Good a state - - -{-| A parser always fails. - - run (fail "bad list") "[1,2,3]" == Err .. - -Seems weird, but it is often useful in combination with -[`oneOf`](#oneOf) or [`andThen`](#andThen). --} -fail : String -> Parser a -fail message = - Parser <| \state -> Bad (Fail message) state - - - --- MAPPING - - -{-| Transform the result of a parser. Maybe you have a value that is -an integer or `null`: - - nullOrInt : Parser (Maybe Int) - nullOrInt = - oneOf - [ map Just int - , map (\_ -> Nothing) (keyword "null") - ] - - -- run nullOrInt "0" == Ok (Just 0) - -- run nullOrInt "13" == Ok (Just 13) - -- run nullOrInt "null" == Ok Nothing - -- run nullOrInt "zero" == Err ... - --} -map : (a -> b) -> Parser a -> Parser b -map func (Parser parse) = - Parser <| \state1 -> - case parse state1 of - Good a state2 -> - Good (func a) state2 - - Bad x state2 -> - Bad x state2 - - -{-| **This function is not used much in practice.** It is nicer to use -the [parser pipeline][pp] operators [`(|.)`](#|.) and [`(|=)`](#|=) -instead. - -[pp]: https://github.com/elm-tools/parser/blob/master/README.md#parser-pipeline - -That said, this function can combine two parsers. Maybe you -want to parse some spaces followed by an integer: - - spacesThenInt : Parser Int - spacesThenInt = - map2 (\_ n -> n) spaces int - - spaces : Parser () - spaces = - ignore zeroOrMore (\char -> char == ' ') - -We can also use `map2` to define `(|.)` and `(|=)` like this: - - (|.) : Parser keep -> Parser ignore -> Parser keep - (|.) keepParser ignoreParser = - map2 (\keep _ -> keep) keepParser ignoreParser - - (|=) : Parser (a -> b) -> Parser a -> Parser b - (|=) funcParser argParser = - map2 (\func arg -> func arg) funcParser argParser --} -map2 : (a -> b -> value) -> Parser a -> Parser b -> Parser value -map2 func (Parser parseA) (Parser parseB) = - Parser <| \state1 -> - case parseA state1 of - Bad x state2 -> - Bad x state2 - - Good a state2 -> - case parseB state2 of - Bad x state3 -> - Bad x state3 - - Good b state3 -> - Good (func a b) state3 - - -{-| **Keep** a value in a parser pipeline. - -Read about parser pipelines **[here][]**. They are really nice! - -[here]: https://github.com/elm-tools/parser/blob/master/README.md#parser-pipeline --} -(|=) : Parser (a -> b) -> Parser a -> Parser b -(|=) parseFunc parseArg = - map2 apply parseFunc parseArg - - -apply : (a -> b) -> a -> b -apply f a = - f a - - -{-| **Ignore** a value in a parser pipeline. - -Read about parser pipelines **[here][]**. They are really nice! - -[here]: https://github.com/elm-tools/parser/blob/master/README.md#parser-pipeline --} -(|.) : Parser keep -> Parser ignore -> Parser keep -(|.) keepParser ignoreParser = - map2 always keepParser ignoreParser - - -infixl 5 |. -infixl 5 |= - - - --- AND THEN - - -{-| Run a parser *and then* run another parser! --} -andThen : (a -> Parser b) -> Parser a -> Parser b -andThen callback (Parser parseA) = - Parser <| \state1 -> - case parseA state1 of - Bad x state2 -> - Bad x state2 - - Good a state2 -> - let - (Parser parseB) = - callback a - in - parseB state2 - - - --- LAZY - - -{-| Helper to define recursive parsers. Say we want a parser for simple -boolean expressions: - - true - false - (true || false) - (true || (true || false)) - -Notice that a boolean expression might contain *other* boolean expressions. -That means we will want to define our parser in terms of itself: - - type Boolean - = MyTrue - | MyFalse - | MyOr Boolean Boolean - - boolean : Parser Boolean - boolean = - oneOf - [ succeed MyTrue - |. keyword "true" - , succeed MyFalse - |. keyword "false" - , succeed MyOr - |. symbol "(" - |. spaces - |= lazy (\_ -> boolean) - |. spaces - |. symbol "||" - |. spaces - |= lazy (\_ -> boolean) - |. spaces - |. symbol ")" - ] - - spaces : Parser () - spaces = - ignore zeroOrMore (\char -> char == ' ') - -**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. - -**Note:** In some cases, it may be more natural or efficient to use -`andThen` to hide a self-reference behind a function. --} -lazy : (() -> Parser a) -> Parser a -lazy thunk = - Parser <| \state -> - let - (Parser parse) = - thunk () - in - parse state - - - --- ONE OF - - -{-| Try a bunch of different parsers. If a parser does not commit, we -move on and try the next one. If a parser *does* commit, we give up on any -remaining parsers. - -The idea is: if you make progress and commit to a parser, you want to -get error messages from *that path*. If you bactrack and keep trying stuff -you will get a much less precise error. - -So say we are parsing “language terms” that include integers and lists -of integers: - - term : Parser Expr - term = - oneOf - [ listOf int - , int - ] - - listOf : Parser a -> Parser (List a) - listOf parser = - succeed identity - |. symbol "[" - |. spaces - ... - -When we get to `oneOf`, we first try the `listOf int` parser. If we see a -`[` we *commit* to that parser. That means if something goes wrong, we do -not backtrack. Instead the parse fails! If we do not see a `[` we move on -to the second option and just try the `int` parser. --} -oneOf : List (Parser a) -> Parser a -oneOf parsers = - Parser <| \state -> oneOfHelp state [] parsers - - -oneOfHelp : State -> List Problem -> List (Parser a) -> Step a -oneOfHelp state problems parsers = - case parsers of - [] -> - Bad (BadOneOf (List.reverse problems)) state - - Parser parse :: remainingParsers -> - case parse state of - Good _ _ as step -> - step - - Bad problem { row, col } as step -> - if state.row == row && state.col == col then - oneOfHelp state (problem :: problems) remainingParsers - - else - step - - - --- REPEAT - - -{-| Try to use the parser as many times as possible. Say we want to parse -`NaN` a bunch of times: - - batman : Parser Int - batman = - map List.length (repeat zeroOrMore (keyword "NaN")) - - -- run batman "whatever" == Ok 0 - -- run batman "" == Ok 0 - -- run batman "NaN" == Ok 1 - -- run batman "NaNNaN" == Ok 2 - -- run batman "NaNNaNNaN" == Ok 3 - -- run batman "NaNNaN batman!" == Ok 2 - -**Note:** If you are trying to parse things like `[1,2,3]` or `{ x = 3 }` -check out the [`list`](Parser-LanguageKit#list) and -[`record`](Parser-LanguageKit#record) functions in the -[`Parser.LanguageKit`](Parser-LanguageKit) module. --} -repeat : Count -> Parser a -> Parser (List a) -repeat count (Parser parse) = - case count of - Exactly n -> - Parser <| \state -> - repeatExactly n parse [] state - - AtLeast n -> - Parser <| \state -> - repeatAtLeast n parse [] state - - -repeatExactly : Int -> (State -> Step a) -> List a -> State -> Step (List a) -repeatExactly n parse revList state1 = - if n <= 0 then - Good (List.reverse revList) state1 - - else - case parse state1 of - Good a state2 -> - if state1.row == state2.row && state1.col == state2.col then - Bad BadRepeat state2 - else - repeatExactly (n - 1) parse (a :: revList) state2 - - Bad x state2 -> - Bad x state2 - - -repeatAtLeast : Int -> (State -> Step a) -> List a -> State -> Step (List a) -repeatAtLeast n parse revList state1 = - case parse state1 of - Good a state2 -> - if state1.row == state2.row && state1.col == state2.col then - Bad BadRepeat state2 - else - repeatAtLeast (n - 1) parse (a :: revList) state2 - - Bad x state2 -> - if state1.row == state2.row && state1.col == state2.col && n <= 0 then - Good (List.reverse revList) state1 - - else - Bad x state2 - - - --- DELAYED COMMIT - - -{-| Only commit if `Parser a` succeeds and `Parser value` makes some progress. - -This is very important for generating high quality error messages! Read more -about this [here][1] and [here][2]. - -[1]: https://github.com/elm-tools/parser/blob/master/README.md#delayed-commits -[2]: https://github.com/elm-tools/parser/blob/master/comparison.md --} -delayedCommit : Parser a -> Parser value -> Parser value -delayedCommit filler realStuff = - delayedCommitMap (\_ v -> v) filler realStuff - - -{-| Like [`delayedCommit`](#delayedCommit), but lets you extract values from -both parsers. Read more about it [here][1] and [here][2]. - -[1]: https://github.com/elm-tools/parser/blob/master/README.md#delayed-commits -[2]: https://github.com/elm-tools/parser/blob/master/comparison.md --} -delayedCommitMap : (a -> b -> value) -> Parser a -> Parser b -> Parser value -delayedCommitMap func (Parser parseA) (Parser parseB) = - Parser <| \state1 -> - case parseA state1 of - Bad x _ -> - Bad x state1 - - Good a state2 -> - case parseB state2 of - Good b state3 -> - Good (func a b) state3 - - Bad x state3 -> - if state2.row == state3.row && state2.col == state3.col then - Bad x state1 - else - Bad x state3 - - - --- SYMBOLS and KEYWORDS - - -{-| Parse symbols like `,`, `(`, and `&&`. - - run (symbol "[") "[" == Ok () - run (symbol "[") "4" == Err ... (ExpectingSymbol "[") ... --} -symbol : String -> Parser () -symbol str = - token ExpectingSymbol str - - -{-| Parse keywords like `let`, `case`, and `type`. - - run (keyword "let") "let" == Ok () - run (keyword "let") "var" == Err ... (ExpectingKeyword "let") ... --} -keyword : String -> Parser () -keyword str = - token ExpectingKeyword str - - -token : (String -> Problem) -> String -> Parser () -token makeProblem str = - Parser <| \({ source, offset, indent, context, row, col } as state) -> - let - (newOffset, newRow, newCol) = - Prim.isSubString str offset row col source - in - if newOffset == -1 then - Bad (makeProblem str) state - - else - Good () - { source = source - , offset = newOffset - , indent = indent - , context = context - , row = newRow - , col = newCol - } - - --- INT - - -{-| Parse integers. It accepts decimal and hexidecimal formats. - - -- decimal - run int "1234" == Ok 1234 - run int "1.34" == Err ... - run int "1e31" == Err ... - run int "123a" == Err ... - run int "0123" == Err ... - - -- hexidecimal - run int "0x001A" == Ok 26 - run int "0x001a" == Ok 26 - run int "0xBEEF" == Ok 48879 - run int "0x12.0" == Err ... - run int "0x12an" == Err ... - -**Note:** If you want a parser for both `Int` and `Float` literals, -check out [`Parser.LanguageKit.number`](Parser-LanguageKit#number). -It does not backtrack, so it should be faster and give better error -messages than using `oneOf` and combining `int` and `float` yourself. - -**Note:** If you want to enable octal or binary `Int` literals, -check out [`Parser.LanguageKit.int`](Parser-LanguageKit#int). --} -int : Parser Int -int = - Parser <| \{ source, offset, indent, context, row, col } -> - case intHelp offset (Prim.isSubChar isZero offset source) source of - Err badOffset -> - Bad BadInt - { source = source - , offset = badOffset - , indent = indent - , context = context - , row = row - , col = col + (badOffset - offset) - } - - Ok goodOffset -> - case String.toInt (String.slice offset goodOffset source) of - Err _ -> - Debug.crash badIntMsg - - Ok n -> - Good n - { source = source - , offset = goodOffset - , indent = indent - , context = context - , row = row - , col = col + (goodOffset - offset) - } - - -intHelp : Int -> Int -> String -> Result Int Int -intHelp offset zeroOffset source = - if zeroOffset == -1 then - Internal.chompDigits Char.isDigit offset source - - else if Prim.isSubChar isX zeroOffset source /= -1 then - Internal.chompDigits Char.isHexDigit (offset + 2) source - --- else if Prim.isSubChar isO zeroOffset source /= -1 then --- Internal.chompDigits Char.isOctDigit (offset + 2) source - - else if Prim.isSubChar Internal.isBadIntEnd zeroOffset source == -1 then - Ok zeroOffset - - else - Err zeroOffset - - -isZero : Char -> Bool -isZero char = - char == '0' - - -isO : Char -> Bool -isO char = - char == 'o' - - -isX : Char -> Bool -isX char = - char == 'x' - - -badIntMsg : String -badIntMsg = - """The `Parser.int` parser seems to have a bug. -Please report an SSCCE to .""" - - - --- FLOAT - - -{-| Parse floats. - - run float "123" == Ok 123 - run float "3.1415" == Ok 3.1415 - run float "0.1234" == Ok 0.1234 - run float ".1234" == Ok 0.1234 - run float "1e-42" == Ok 1e-42 - run float "6.022e23" == Ok 6.022e23 - run float "6.022E23" == Ok 6.022e23 - run float "6.022e+23" == Ok 6.022e23 - run float "6.022e" == Err .. - run float "6.022n" == Err .. - run float "6.022.31" == Err .. - -**Note:** If you want a parser for both `Int` and `Float` literals, -check out [`Parser.LanguageKit.number`](Parser-LanguageKit#number). -It does not backtrack, so it should be faster and give better error -messages than using `oneOf` and combining `int` and `float` yourself. - -**Note:** If you want to disable literals like `.123` like Elm, -check out [`Parser.LanguageKit.float`](Parser-LanguageKit#float). --} -float : Parser Float -float = - Parser <| \{ source, offset, indent, context, row, col } -> - case floatHelp offset (Prim.isSubChar isZero offset source) source of - Err badOffset -> - Bad BadFloat - { source = source - , offset = badOffset - , indent = indent - , context = context - , row = row - , col = col + (badOffset - offset) - } - - Ok goodOffset -> - case String.toFloat (String.slice offset goodOffset source) of - Err _ -> - Debug.crash badFloatMsg - - Ok n -> - Good n - { source = source - , offset = goodOffset - , indent = indent - , context = context - , row = row - , col = col + (goodOffset - offset) - } - - -floatHelp : Int -> Int -> String -> Result Int Int -floatHelp offset zeroOffset source = - if zeroOffset >= 0 then - Internal.chompDotAndExp zeroOffset source - - else - let - dotOffset = - Internal.chomp Char.isDigit offset source - - result = - Internal.chompDotAndExp dotOffset source - in - case result of - Err _ -> - result - - Ok n -> - if n == offset then Err n else result - - -badFloatMsg : String -badFloatMsg = - """The `Parser.float` parser seems to have a bug. -Please report an SSCCE to .""" - - - --- END - - -{-| Check if you have reached the end of the string you are parsing. - - justAnInt : Parser Int - justAnInt = - succeed identity - |= int - |. 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. --} -end : Parser () -end = - Parser <| \state -> - if String.length state.source == state.offset then - Good () state - - else - Bad ExpectingEnd state - - - --- SOURCE - - -{-| Run a parser, but return the underlying source code that actually -got parsed. - - -- run (source (ignore oneOrMore Char.isLower)) "abc" == Ok "abc" - -- keep count isOk = source (ignore count isOk) - -This becomes a useful optimization when you need to [`keep`](#keep) -something very specific. For example, say we want to parse capitalized -words: - - import Char - - variable : Parser String - variable = - succeed (++) - |= keep (Exactly 1) Char.isUpper - |= keep zeroOrMore Char.isLower - -In this case, each `keep` allocates a string. Then we use `(++)` to create the -final string. That means *three* strings are allocated. - -In contrast, using `source` with `ignore` lets you grab the final string -directly. It tracks where the parser starts and ends, so it can use -`String.slice` to grab that part directly. - - variable : Parser String - variable = - source <| - ignore (Exactly 1) Char.isUpper - |. ignore zeroOrMore Char.isLower - -This version only allocates *one* string. --} -source : Parser a -> Parser String -source parser = - sourceMap always parser - - -{-| Like `source`, but it allows you to combine the source string -with the value that is produced by the parser. So maybe you want -a float, but you also want to know exactly how it looked. - - number : Parser (String, Float) - number = - sourceMap (,) float - - -- run number "100" == Ok ("100", 100) - -- run number "1e2" == Ok ("1e2", 100) --} -sourceMap : (String -> a -> b) -> Parser a -> Parser b -sourceMap func (Parser parse) = - Parser <| \({source, offset} as state1) -> - case parse state1 of - Bad x state2 -> - Bad x state2 - - Good a state2 -> - let - subString = - String.slice offset state2.offset source - in - Good (func subString a) state2 - - - --- REPEAT - - -{-| How many characters to [`keep`](#keep) or [`ignore`](#ignore). --} -type Count = AtLeast Int | Exactly Int - - -{-| A simple alias for `AtLeast 0` so your code reads nicer: - - import Char - - spaces : Parser String - spaces = - keep zeroOrMore (\c -> c == ' ') - - -- same as: keep (AtLeast 0) (\c -> c == ' ') --} -zeroOrMore : Count -zeroOrMore = - AtLeast 0 - - -{-| A simple alias for `AtLeast 1` so your code reads nicer: - - import Char - - lows : Parser String - lows = - keep oneOrMore Char.isLower - - -- same as: keep (AtLeast 1) Char.isLower --} -oneOrMore : Count -oneOrMore = - AtLeast 1 - - -{-| Keep some characters. If you want a capital letter followed by -zero or more lower case letters, you could say: - - import Char - - capitalized : Parser String - capitalized = - succeed (++) - |= keep (Exactly 1) Char.isUpper - |= keep zeroOrMore Char.isLower - - -- good: Cat, Tom, Sally - -- bad: cat, tom, TOM, tOm - -**Note:** Check out [`source`](#source) for a more efficient -way to grab the underlying source of a complex parser. --} -keep : Count -> (Char -> Bool) -> Parser String -keep count predicate = - source (ignore count predicate) - - -{-| Ignore some characters. If you want to ignore one or more -spaces, you might say: - - spaces : Parser () - spaces = - ignore oneOrMore (\c -> c == ' ') - --} -ignore : Count -> (Char -> Bool) -> Parser () -ignore count predicate = - case count of - Exactly n -> - Parser <| \{ source, offset, indent, context, row, col } -> - ignoreExactly n predicate source offset indent context row col - - AtLeast n -> - Parser <| \{ source, offset, indent, context, row, col } -> - ignoreAtLeast n predicate source offset indent context row col - - -ignoreExactly : Int -> (Char -> Bool) -> String -> Int -> Int -> List Context -> Int -> Int -> Step () -ignoreExactly n predicate source offset indent context row col = - if n <= 0 then - Good () - { source = source - , offset = offset - , indent = indent - , context = context - , row = row - , col = col - } - - else - let - newOffset = - Prim.isSubChar predicate offset source - in - if newOffset == -1 then - Bad BadRepeat - { source = source - , offset = offset - , indent = indent - , context = context - , row = row - , col = col - } - - else if newOffset == -2 then - ignoreExactly (n - 1) predicate source (offset + 1) indent context (row + 1) 1 - - else - ignoreExactly (n - 1) predicate source newOffset indent context row (col + 1) - - -ignoreAtLeast : Int -> (Char -> Bool) -> String -> Int -> Int -> List Context -> Int -> Int -> Step () -ignoreAtLeast n predicate source offset indent context row col = - let - newOffset = - Prim.isSubChar predicate offset source - in - -- no match - if newOffset == -1 then - let - state = - { source = source - , offset = offset - , indent = indent - , context = context - , row = row - , col = col - } - in - if n <= 0 then Good () state else Bad BadRepeat state - - -- matched a newline - else if newOffset == -2 then - ignoreAtLeast (n - 1) predicate source (offset + 1) indent context (row + 1) 1 - - -- normal match - else - ignoreAtLeast (n - 1) predicate source newOffset indent context row (col + 1) - - - --- IGNORE UNTIL - - -{-| Ignore characters until *after* the given string. -So maybe we want to parse Elm-style single-line comments: - - elmComment : Parser () - elmComment = - symbol "--" - |. ignoreUntil "\n" - -Or maybe you want to parse JS-style multi-line comments: - - jsComment : Parser () - jsComment = - symbol "/*" - |. ignoreUntil "*/" - -**Note:** You must take more care when parsing Elm-style multi-line -comments. Elm can recognize nested comments, but the `jsComment` parser -cannot. See [`Parser.LanguageKit.whitespace`](Parser-LanguageKit#whitespace) -for help with this. --} -ignoreUntil : String -> Parser () -ignoreUntil str = - Parser <| \({ source, offset, indent, context, row, col } as state) -> - let - (newOffset, newRow, newCol) = - Prim.findSubString False str offset row col source - in - if newOffset == -1 then - Bad (ExpectingClosing str) state - - else - Good () - { source = source - , offset = newOffset - , indent = indent - , context = context - , row = newRow - , col = newCol - } - - - --- CONTEXT - - -{-| Specify what you are parsing right now. So if you have a parser -for lists like `[ 1, 2, 3 ]` you could say: - - list : Parser (List Int) - list = - inContext "list" <| - succeed identity - |. symbol "[" - |. spaces - |= commaSep int - |. spaces - |. symbol "]" - - -- spaces : Parser () - -- commaSep : Parser a -> Parser (List a) - -Now you get that extra context information if there is a parse error anywhere -in the list. For example, if you have `[ 1, 23zm5, 3 ]` you could generate an -error message like this: - - I ran into a problem while parsing this list: - - [ 1, 23zm5, 3 ] - ^ - Looking for a valid integer, like 6 or 90210. - -Notice that the error message knows you are parsing a list right now! --} -inContext : String -> Parser a -> Parser a -inContext ctx (Parser parse) = - Parser <| \({ context, row, col } as initialState) -> - let - state1 = - changeContext (Context row col ctx :: context) initialState - in - case parse state1 of - Good a state2 -> - Good a (changeContext context state2) - - Bad _ _ as step -> - step - - -changeContext : List Context -> State -> State -changeContext newContext { source, offset, indent, row, col } = - { source = source - , offset = offset - , indent = indent - , context = newContext - , row = row - , col = col - } diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/Internal.elm b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/Internal.elm deleted file mode 100644 index cd07186..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/Internal.elm +++ /dev/null @@ -1,149 +0,0 @@ -module Parser.Internal exposing - ( Parser(..) - , Step(..) - , State - , chomp - , chompDigits - , chompDotAndExp - , isBadIntEnd - ) - - -import Char -import ParserPrimitives as Prim - - - --- PARSERS - - -type Parser ctx x a = - Parser (State ctx -> Step ctx x a) - - -type Step ctx x a - = Good a (State ctx) - | Bad x (State ctx) - - -type alias State ctx = - { source : String - , offset : Int - , indent : Int - , context : List ctx - , row : Int - , col : Int - } - - - --- CHOMPERS - - -chomp : (Char -> Bool) -> Int -> String -> Int -chomp isGood offset source = - let - newOffset = - Prim.isSubChar isGood offset source - in - if newOffset < 0 then - offset - - else - chomp isGood newOffset source - - - --- CHOMP DIGITS - - -chompDigits : (Char -> Bool) -> Int -> String -> Result Int Int -chompDigits isValidDigit offset source = - let - newOffset = - chomp isValidDigit offset source - in - -- no digits - if newOffset == offset then - Err newOffset - - -- ends with non-digit characters - else if Prim.isSubChar isBadIntEnd newOffset source /= -1 then - Err newOffset - - -- all valid digits! - else - Ok newOffset - - -isBadIntEnd : Char -> Bool -isBadIntEnd char = - Char.isDigit char - || Char.isUpper char - || Char.isLower char - || char == '.' - - - --- CHOMP FLOAT STUFF - - -chompDotAndExp : Int -> String -> Result Int Int -chompDotAndExp offset source = - let - dotOffset = - Prim.isSubChar isDot offset source - in - if dotOffset == -1 then - chompExp offset source - - else - chompExp (chomp Char.isDigit dotOffset source) source - - -isDot : Char -> Bool -isDot char = - char == '.' - - -chompExp : Int -> String -> Result Int Int -chompExp offset source = - let - eOffset = - Prim.isSubChar isE offset source - in - if eOffset == -1 then - Ok offset - - else - let - opOffset = - Prim.isSubChar isPlusOrMinus eOffset source - - expOffset = - if opOffset == -1 then eOffset else opOffset - in - if Prim.isSubChar isZero expOffset source /= -1 then - Err expOffset - - else if Prim.isSubChar Char.isDigit expOffset source == -1 then - Err expOffset - - else - chompDigits Char.isDigit expOffset source - - -isE : Char -> Bool -isE char = - char == 'e' || char == 'E' - - -isZero : Char -> Bool -isZero char = - char == '0' - - -isPlusOrMinus : Char -> Bool -isPlusOrMinus char = - char == '+' || char == '-' - diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/LanguageKit.elm b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/LanguageKit.elm deleted file mode 100644 index 4b6e4ea..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/LanguageKit.elm +++ /dev/null @@ -1,509 +0,0 @@ -module Parser.LanguageKit exposing - ( variable - , list, record, tuple, sequence, Trailing(..) - , whitespace, LineComment(..), MultiComment(..) - ) - - -{-| - -# Variables -@docs variable - -# Lists, records, and that sort of thing -@docs list, record, tuple, sequence, Trailing - -# Whitespace -@docs whitespace, LineComment, MultiComment - --} - - -import Set exposing (Set) -import Parser exposing (..) -import Parser.Internal as I exposing (Step(..), State) -import ParserPrimitives as Prim - - - --- VARIABLES - - -{-| Create a parser for variables. It takes two `Char` checkers. The -first one is for the first character. The second one is for all the -other characters. - -In Elm, we distinguish between upper and lower case variables, so we -can do something like this: - - import Char - import Parser exposing (..) - import Parser.LanguageKit exposing (variable) - import Set - - lowVar : Parser String - lowVar = - variable Char.isLower isVarChar keywords - - capVar : Parser String - capVar = - variable Char.isUpper isVarChar keywords - - isVarChar : Char -> Bool - isVarChar char = - Char.isLower char - || Char.isUpper char - || Char.isDigit char - || char == '_' - - keywords : Set.Set String - keywords = - Set.fromList [ "let", "in", "case", "of" ] --} -variable : (Char -> Bool) -> (Char -> Bool) -> Set String -> Parser String -variable isFirst isOther keywords = - I.Parser <| \({ source, offset, indent, context, row, col } as state1) -> - let - firstOffset = - Prim.isSubChar isFirst offset source - in - if firstOffset == -1 then - Bad ExpectingVariable state1 - - else - let - state2 = - if firstOffset == -2 then - varHelp isOther (offset + 1) (row + 1) 1 source indent context - else - varHelp isOther firstOffset row (col + 1) source indent context - - name = - String.slice offset state2.offset source - in - if Set.member name keywords then - Bad ExpectingVariable state1 - - else - Good name state2 - - -varHelp : (Char -> Bool) -> Int -> Int -> Int -> String -> Int -> List ctx -> State ctx -varHelp isGood offset row col source indent context = - let - newOffset = - Prim.isSubChar isGood offset source - in - if newOffset == -1 then - { source = source - , offset = offset - , indent = indent - , context = context - , row = row - , col = col - } - - else if newOffset == -2 then - varHelp isGood (offset + 1) (row + 1) 1 source indent context - - else - varHelp isGood newOffset row (col + 1) source indent context - - - --- SEQUENCES - - -{-| Parse a comma-separated list like `[ 1, 2, 3 ]`. You provide -a parser for the spaces and for the list items. So if you want -to parse a list of integers, you would say: - - import Parser exposing (Parser) - import Parser.LanguageKit as Parser - - intList : Parser (List Int) - intList = - Parser.list spaces Parser.int - - spaces : Parser () - spaces = - Parser.ignore zeroOrMore (\char -> char == ' ') - - -- run intList "[]" == Ok [] - -- run intList "[ ]" == Ok [] - -- run intList "[1,2,3]" == Ok [1,2,3] - -- run intList "[ 1, 2, 3 ]" == Ok [1,2,3] - -- run intList "[ 1 , 2 , 3 ]" == Ok [1,2,3] - -- run intList "[ 1, 2, 3, ]" == Err ... - -- run intList "[, 1, 2, 3 ]" == Err ... - -**Note:** If you want trailing commas, check out the -[`sequence`](#sequence) function. --} -list : Parser () -> Parser a -> Parser (List a) -list spaces item = - sequence - { start = "[" - , separator = "," - , end = "]" - , spaces = spaces - , item = item - , trailing = Forbidden - } - - -{-| Help parse records like `{ a = 2, b = 2 }`. You provide -a parser for the spaces and for the list items, you might say: - - import Parser exposing ( Parser, (|.), (|=), zeroOrMore ) - import Parser.LanguageKit as Parser - - record : Parser (List (String, Int)) - record = - Parser.record spaces field - - field : Parser (String, Int) - field = - Parser.succeed (,) - |= lowVar - |. spaces - |. Parser.symbol "=" - |. spaces - |= int - - spaces : Parser () - spaces = - Parser.ignore zeroOrMore (\char -> char == ' ') - - -- run record "{}" == Ok [] - -- run record "{ }" == Ok [] - -- run record "{ x = 3 }" == Ok [ ("x",3) ] - -- run record "{ x = 3, }" == Err ... - -- run record "{ x = 3, y = 4 }" == Ok [ ("x",3), ("y",4) ] - -- run record "{ x = 3, y = }" == Err ... - -**Note:** If you want trailing commas, check out the -[`sequence`](#sequence) function. --} -record : Parser () -> Parser a -> Parser (List a) -record spaces item = - sequence - { start = "{" - , separator = "," - , end = "}" - , spaces = spaces - , item = item - , trailing = Forbidden - } - - -{-| Help parse tuples like `(3, 4)`. Works just like [`list`](#list) -and [`record`](#record). And if you need something custom, check out -the [`sequence`](#sequence) function. --} -tuple : Parser () -> Parser a -> Parser (List a) -tuple spaces item = - sequence - { start = "(" - , separator = "," - , end = ")" - , spaces = spaces - , item = item - , trailing = Forbidden - } - - -{-| Handle things *like* lists and records, but you can customize the -details however you need. Say you want to parse C-style code blocks: - - import Parser exposing (Parser) - import Parser.LanguageKit as Parser exposing (Trailing(..)) - - block : Parser (List Stmt) - block = - Parser.sequence - { start = "{" - , separator = ";" - , end = "}" - , spaces = spaces - , item = statement - , trailing = Mandatory -- demand a trailing semi-colon - } - - -- spaces : Parser () - -- statement : Parser Stmt - -**Note:** If you need something more custom, do not be afraid to check -out the implementation and customize it for your case. It is better to -get nice error messages with a lower-level implementation than to try -to hack high-level parsers to do things they are not made for. --} -sequence - : { start : String - , separator : String - , end : String - , spaces : Parser () - , item : Parser a - , trailing : Trailing - } - -> Parser (List a) -sequence { start, end, spaces, item, separator, trailing } = - symbol start - |- spaces - |- sequenceEnd end spaces item separator trailing - - -{-| What’s the deal with trailing commas? Are they `Forbidden`? -Are they `Optional`? Are they `Mandatory`? Welcome to [shapes -club](http://poorlydrawnlines.com/comic/shapes-club/)! --} -type Trailing = Forbidden | Optional | Mandatory - - -ignore : Parser ignore -> Parser keep -> Parser keep -ignore ignoreParser keepParser = - map2 revAlways ignoreParser keepParser - - -(|-) : Parser ignore -> Parser keep -> Parser keep -(|-) = - ignore - - -revAlways : ignore -> keep -> keep -revAlways _ keep = - keep - - -sequenceEnd : String -> Parser () -> Parser a -> String -> Trailing -> Parser (List a) -sequenceEnd end spaces parseItem sep trailing = - let - chompRest item = - case trailing of - Forbidden -> - sequenceEndForbidden end spaces parseItem sep [item] - - Optional -> - sequenceEndOptional end spaces parseItem sep [item] - - Mandatory -> - spaces - |- symbol sep - |- spaces - |- sequenceEndMandatory end spaces parseItem sep [item] - in - oneOf - [ parseItem - |> andThen chompRest - , symbol end - |- succeed [] - ] - - -sequenceEndForbidden : String -> Parser () -> Parser a -> String -> List a -> Parser (List a) -sequenceEndForbidden end spaces parseItem sep revItems = - let - chompRest item = - sequenceEndForbidden end spaces parseItem sep (item :: revItems) - in - ignore spaces <| - oneOf - [ symbol sep - |- spaces - |- andThen chompRest parseItem - , symbol end - |- succeed (List.reverse revItems) - ] - - -sequenceEndOptional : String -> Parser () -> Parser a -> String -> List a -> Parser (List a) -sequenceEndOptional end spaces parseItem sep revItems = - let - parseEnd = - andThen (\_ -> succeed (List.reverse revItems)) (symbol end) - - chompRest item = - sequenceEndOptional end spaces parseItem sep (item :: revItems) - in - ignore spaces <| - oneOf - [ symbol sep - |- spaces - |- oneOf [ andThen chompRest parseItem, parseEnd ] - , parseEnd - ] - - -sequenceEndMandatory : String -> Parser () -> Parser a -> String -> List a -> Parser (List a) -sequenceEndMandatory end spaces parseItem sep revItems = - let - chompRest item = - sequenceEndMandatory end spaces parseItem sep (item :: revItems) - in - oneOf - [ andThen chompRest <| - parseItem - |. spaces - |. symbol sep - |. spaces - , symbol end - |- succeed (List.reverse revItems) - ] - - - --- WHITESPACE - - -{-| Create a custom whitespace parser. It will always chomp the -`' '`, `'\r'`, and `'\n'` characters, but you can customize some -other things. Here are some examples: - - elm : Parser () - elm = - whitespace - { allowTabs = False - , lineComment = LineComment "--" - , multiComment = NestableComment "{-" "-}" - } - - js : Parser () - js = - whitespace - { allowTabs = True - , lineComment = LineComment "//" - , multiComment = UnnestableComment "/*" "*/" - } - -If you need further customization, please open an issue describing your -scenario or check out the source code and write it yourself. This is all -built using stuff from the root `Parser` module. --} -whitespace - : { allowTabs : Bool - , lineComment : LineComment - , multiComment : MultiComment - } - -> Parser () -whitespace { allowTabs, lineComment, multiComment } = - let - tabParser = - if allowTabs then - [ Parser.ignore zeroOrMore isTab ] - else - [] - - lineParser = - case lineComment of - NoLineComment -> - [] - - LineComment start -> - [ symbol start - |. ignoreUntil "\n" - ] - - multiParser = - case multiComment of - NoMultiComment -> - [] - - UnnestableComment start end -> - [ symbol start - |. ignoreUntil end - ] - - NestableComment start end -> - [ nestableComment start end - ] - in - whitespaceHelp <| - oneOf (tabParser ++ lineParser ++ multiParser) - - -chompSpaces : Parser () -chompSpaces = - Parser.ignore zeroOrMore isSpace - - -isSpace : Char -> Bool -isSpace char = - char == ' ' || char == '\n' || char == '\r' - - -isTab : Char -> Bool -isTab char = - char == '\t' - - -whitespaceHelp : Parser a -> Parser () -whitespaceHelp parser = - ignore chompSpaces <| - oneOf [ andThen (\_ -> whitespaceHelp parser) parser, succeed () ] - - -{-| Are line comments allowed? If so, what symbol do they start with? - - LineComment "--" -- Elm - LineComment "//" -- JS - LineComment "#" -- Python - NoLineComment -- OCaml --} -type LineComment = NoLineComment | LineComment String - - -{-| Are multi-line comments allowed? If so, what symbols do they start -and end with? - - NestableComment "{-" "-}" -- Elm - UnnestableComment "/*" "*/" -- JS - NoMultiComment -- Python - -In Elm, you can nest multi-line comments. In C-like languages, like JS, -this is not allowed. As soon as you see a `*/` the comment is over no -matter what. --} -type MultiComment - = NoMultiComment - | NestableComment String String - | UnnestableComment String String - - -nestableComment : String -> String -> Parser () -nestableComment start end = - case (String.uncons start, String.uncons end) of - (Nothing, _) -> - fail "Trying to parse a multi-line comment, but the start token cannot be the empty string!" - - (_, Nothing) -> - fail "Trying to parse a multi-line comment, but the end token cannot be the empty string!" - - ( Just (startChar, _), Just (endChar, _) ) -> - let - isNotRelevant char = - char /= startChar && char /= endChar - in - symbol start - |. nestableCommentHelp isNotRelevant start end 1 - - -nestableCommentHelp : (Char -> Bool) -> String -> String -> Int -> Parser () -nestableCommentHelp isNotRelevant start end nestLevel = - lazy <| \_ -> - ignore (Parser.ignore zeroOrMore isNotRelevant) <| - oneOf - [ ignore (symbol end) <| - if nestLevel == 1 then - succeed () - else - nestableCommentHelp isNotRelevant start end (nestLevel - 1) - , ignore (symbol start) <| - nestableCommentHelp isNotRelevant start end (nestLevel + 1) - , ignore (Parser.ignore (Exactly 1) isChar) <| - nestableCommentHelp isNotRelevant start end nestLevel - ] - - -isChar : Char -> Bool -isChar char = - True diff --git a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/LowLevel.elm b/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/LowLevel.elm deleted file mode 100644 index 653c347..0000000 --- a/finished/elm-stuff/packages/elm-tools/parser/2.0.1/src/Parser/LowLevel.elm +++ /dev/null @@ -1,118 +0,0 @@ -module Parser.LowLevel exposing - ( getIndentLevel - , withIndentLevel - - , getPosition - , getRow - , getCol - - , getOffset - , getSource - ) - -{-| You are unlikely to need any of this under normal circumstances. - -# Indentation -@docs getIndentLevel, withIndentLevel - -# Row, Column, Offset, and Source -@docs getPosition, getRow, getCol, getOffset, getSource - --} - -import Parser exposing (Parser) -import Parser.Internal as I exposing (State) - - - --- INDENTATION - - -{-| This parser tracks “indentation level” so you can parse indentation -sensitive languages. Indentation levels correspond to column numbers, so -it starts at 1. --} -getIndentLevel : Parser Int -getIndentLevel = - I.Parser <| \state -> I.Good state.indent state - - -{-| Run a parser with a given indentation level. So you will likely -use `getCol` to get the current column, `andThen` give that to -`withIndentLevel`. --} -withIndentLevel : Int -> Parser a -> Parser a -withIndentLevel newIndent (I.Parser parse) = - I.Parser <| \state1 -> - case parse (changeIndent newIndent state1) of - I.Good a state2 -> - I.Good a (changeIndent state1.indent state2) - - I.Bad x state2 -> - I.Bad x (changeIndent state1.indent state2) - - -changeIndent : Int -> State ctx -> State ctx -changeIndent newIndent { source, offset, context, row, col } = - { source = source - , offset = offset - , indent = newIndent - , context = context - , row = row - , col = col - } - - - --- POSITION - - -{-| Code editors treat code like a grid. There are rows and columns. -In most editors, rows and colums are 1-indexed. You move to a new row -whenever you see a `\n` character. - -The `getPosition` parser succeeds with your current row and column -within the string you are parsing. --} -getPosition : Parser (Int, Int) -getPosition = - I.Parser <| \state -> I.Good (state.row, state.col) state - - -{-| The `getRow` parser succeeds with your current row within -the string you are parsing. --} -getRow : Parser Int -getRow = - I.Parser <| \state -> I.Good state.row state - - -{-| The `getCol` parser succeeds with your current column within -the string you are parsing. --} -getCol : Parser Int -getCol = - I.Parser <| \state -> I.Good state.col state - - -{-| Editors think of code as a grid, but behind the scenes it is just -a flat array of UTF16 characters. `getOffset` tells you your index in -that flat array. So if you have read `"\n\n\n\n"` you are on row 5, -column 1, and offset 4. - -**Note:** browsers use UTF16 strings, so characters may be one or two 16-bit -words. This means you can read 4 characters, but your offset will move by 8. --} -getOffset : Parser Int -getOffset = - I.Parser <| \state -> I.Good state.offset state - - -{-| Get the entire string you are parsing right now. Paired with -`getOffset` this can let you use `String.slice` to grab substrings -with very little intermediate allocation. --} -getSource : Parser String -getSource = - I.Parser <| \state -> I.Good state.source state - diff --git a/finished/src/Data/Article.elm b/finished/src/Data/Article.elm index c921d4b..0a4b8ec 100644 --- a/finished/src/Data/Article.elm +++ b/finished/src/Data/Article.elm @@ -3,12 +3,15 @@ module Data.Article ( Article , Body , Slug + , Tag , bodyToHtml , bodyToMarkdownString , decoder , decoderWithBody , slugParser , slugToString + , tagDecoder + , tagToString ) import Data.Article.Author as Author exposing (Author) @@ -107,6 +110,24 @@ slugToString (Slug slug) = +-- TAGS -- + + +type Tag + = Tag String + + +tagToString : Tag -> String +tagToString (Tag slug) = + slug + + +tagDecoder : Decoder Tag +tagDecoder = + Decode.map Tag Decode.string + + + -- BODY -- diff --git a/finished/src/Data/Article/Tag.elm b/finished/src/Data/Article/Tag.elm deleted file mode 100644 index 9f0ee90..0000000 --- a/finished/src/Data/Article/Tag.elm +++ /dev/null @@ -1,55 +0,0 @@ -module Data.Article.Tag - exposing - ( Tag - , decoder - , encode - , listParser - , toString - ) - -import Json.Decode as Decode exposing (Decoder) -import Json.Encode as Encode exposing (Value) -import Parser exposing ((|.), (|=), Parser, end, ignore, keep, oneOrMore, repeat, zeroOrMore) - - -type Tag - = Tag String - - -toString : Tag -> String -toString (Tag str) = - str - - -encode : Tag -> Value -encode (Tag str) = - Encode.string str - - -decoder : Decoder Tag -decoder = - Decode.map Tag Decode.string - - -listParser : Parser (List Tag) -listParser = - Parser.succeed (List.map Tag) - |. ignore zeroOrMore isWhitespace - |= repeat zeroOrMore tag - |. end - - - --- INTERNAL -- - - -tag : Parser String -tag = - keep oneOrMore (\char -> not (isWhitespace char)) - |. ignore zeroOrMore isWhitespace - - -isWhitespace : Char -> Bool -isWhitespace char = - -- Treat hashtags and commas as effectively whitespace; ignore them. - char == '#' || char == ',' || char == ' ' diff --git a/finished/src/Page/Article/Editor.elm b/finished/src/Page/Article/Editor.elm index 2f9274b..7e6d802 100644 --- a/finished/src/Page/Article/Editor.elm +++ b/finished/src/Page/Article/Editor.elm @@ -1,7 +1,6 @@ module Page.Article.Editor exposing (Model, Msg, initEdit, initNew, update, view) import Data.Article as Article exposing (Article, Body) -import Data.Article.Tag as Tag exposing (Tag) import Data.Session exposing (Session) import Data.User exposing (User) import Html exposing (..) @@ -9,7 +8,6 @@ import Html.Attributes exposing (attribute, class, defaultValue, disabled, href, import Html.Events exposing (onInput, onSubmit) import Http import Page.Errored exposing (PageLoadError, pageLoadError) -import Parser import Request.Article import Route import Task exposing (Task) @@ -28,7 +26,7 @@ type alias Model = , title : String , body : String , description : String - , tags : String + , tags : List String , isSaving : Bool } @@ -40,7 +38,7 @@ initNew = , title = "" , body = "" , description = "" - , tags = "" + , tags = [] , isSaving = False } @@ -62,7 +60,7 @@ initEdit session slug = , title = article.title , body = Article.bodyToMarkdownString article.body , description = article.description - , tags = String.join " " article.tags + , tags = article.tags , isSaving = False } ) @@ -123,7 +121,7 @@ viewForm model = , Form.input [ placeholder "Enter tags" , onInput SetTags - , defaultValue model.tags + , defaultValue (String.join " " model.tags) ] [] , button [ class "btn btn-lg pull-xs-right btn-primary", disabled model.isSaving ] @@ -154,24 +152,10 @@ update user msg model = [] -> case model.editingArticle of Nothing -> - case Parser.run Tag.listParser model.tags of - Ok tags -> - let - request = - Request.Article.create - { tags = tags - , title = model.title - , body = model.body - , description = model.description - } - user.token - in - request - |> Http.send CreateCompleted - |> pair { model | errors = [], isSaving = True } - - Err _ -> - ( { model | errors = [ ( Tags, "Invalid tags." ) ] }, Cmd.none ) + user.token + |> Request.Article.create model + |> Http.send CreateCompleted + |> pair { model | errors = [], isSaving = True } Just slug -> user.token @@ -189,7 +173,7 @@ update user msg model = ( { model | description = description }, Cmd.none ) SetTags tags -> - ( { model | tags = tags }, Cmd.none ) + ( { model | tags = tagsFromString tags }, Cmd.none ) SetBody body -> ( { model | body = body }, Cmd.none ) @@ -233,7 +217,6 @@ type Field = Form | Title | Body - | Tags type alias Error = @@ -252,6 +235,14 @@ modelValidator = -- INTERNAL -- +tagsFromString : String -> List String +tagsFromString str = + str + |> String.split " " + |> List.map String.trim + |> List.filter (not << String.isEmpty) + + redirectToArticle : Article.Slug -> Cmd msg redirectToArticle = Route.modifyUrl << Route.Article diff --git a/finished/src/Page/Home.elm b/finished/src/Page/Home.elm index 2b04a6c..ffc4995 100644 --- a/finished/src/Page/Home.elm +++ b/finished/src/Page/Home.elm @@ -3,7 +3,7 @@ module Page.Home exposing (Model, Msg, init, update, view) {-| The homepage. You can get here via either the / or /#/ routes. -} -import Data.Article.Tag as Tag exposing (Tag) +import Data.Article as Article exposing (Tag) import Data.Session exposing (Session) import Html exposing (..) import Html.Attributes exposing (attribute, class, classList, href, id, placeholder) @@ -100,7 +100,7 @@ viewTag tagName = , href "javascript:void(0)" , onClick (SelectTag tagName) ] - [ text (Tag.toString tagName) ] + [ text (Article.tagToString tagName) ] diff --git a/finished/src/Request/Article.elm b/finished/src/Request/Article.elm index 8f6fea2..25d8aaf 100644 --- a/finished/src/Request/Article.elm +++ b/finished/src/Request/Article.elm @@ -14,9 +14,8 @@ module Request.Article , update ) -import Data.Article as Article exposing (Article, Body, slugToString) +import Data.Article as Article exposing (Article, Body, Tag, slugToString) import Data.Article.Feed as Feed exposing (Feed) -import Data.Article.Tag as Tag exposing (Tag) import Data.AuthToken exposing (AuthToken, withAuthorization) import Data.User as User exposing (Username) import Http @@ -69,7 +68,7 @@ defaultListConfig = list : ListConfig -> Maybe AuthToken -> Http.Request Feed list config maybeToken = - [ ( "tag", Maybe.map Tag.toString config.tag ) + [ ( "tag", Maybe.map Article.tagToString config.tag ) , ( "author", Maybe.map User.usernameToString config.author ) , ( "favorited", Maybe.map User.usernameToString config.favorited ) , ( "limit", Just (toString config.limit) ) @@ -115,7 +114,7 @@ feed config token = tags : Http.Request (List Tag) tags = - Decode.field "tags" (Decode.list Tag.decoder) + Decode.field "tags" (Decode.list Article.tagDecoder) |> Http.get (apiUrl "/tags") @@ -170,7 +169,7 @@ type alias CreateConfig record = | title : String , description : String , body : String - , tags : List Tag + , tags : List String } @@ -195,7 +194,7 @@ create config token = [ ( "title", Encode.string config.title ) , ( "description", Encode.string config.description ) , ( "body", Encode.string config.body ) - , ( "tagList", Encode.list (List.map Tag.encode config.tags) ) + , ( "tagList", Encode.list (List.map Encode.string config.tags) ) ] body = diff --git a/finished/src/Request/Article/Comments.elm b/finished/src/Request/Article/Comments.elm index 0f7343b..87501c5 100644 --- a/finished/src/Request/Article/Comments.elm +++ b/finished/src/Request/Article/Comments.elm @@ -1,6 +1,6 @@ module Request.Article.Comments exposing (delete, list, post) -import Data.Article as Article exposing (Article, slugToString) +import Data.Article as Article exposing (Article, Tag, slugToString) import Data.Article.Comment as Comment exposing (Comment, CommentId) import Data.AuthToken exposing (AuthToken, withAuthorization) import Http diff --git a/finished/src/Views/Article/Feed.elm b/finished/src/Views/Article/Feed.elm index 6936c88..ce3fd8c 100644 --- a/finished/src/Views/Article/Feed.elm +++ b/finished/src/Views/Article/Feed.elm @@ -16,9 +16,8 @@ overkill, so we use simpler APIs instead. -} -import Data.Article as Article exposing (Article) +import Data.Article as Article exposing (Article, Tag) import Data.Article.Feed exposing (Feed) -import Data.Article.Tag as Tag exposing (Tag) import Data.AuthToken exposing (AuthToken) import Data.Session exposing (Session) import Data.User exposing (Username) @@ -127,7 +126,7 @@ sourceName source = "Global Feed" TagFeed tagName -> - "#" ++ Tag.toString tagName + "#" ++ Article.tagToString tagName FavoritedFeed username -> "Favorited Articles"