Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize StringLike to StreamLike fix #58 #62

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f0ba9e4
Generalize StringLike to StreamLike
safareli May 26, 2017
a991f94
update list instance
safareli Jun 4, 2017
2f59245
fix redundant parens and imports
safareli Jun 4, 2017
fdcb5ba
update lists
safareli Jun 5, 2017
4f74e34
Merge branch 'master' into string
safareli Jun 10, 2017
9ff887b
update description
safareli Jun 10, 2017
2471c05
add script.test
safareli Jun 10, 2017
ad4a76c
remove Token{token,when,match}
safareli Jun 10, 2017
b89442b
add 'drop (Prefix a) a >>= uncons = Nothing' law
safareli Jun 11, 2017
67926be
remove String.whitespace
safareli Jun 18, 2017
453d6a1
rename `String.char` to `String.match`
safareli Jun 18, 2017
96dc7da
rename `String.anyChar` to `String.token`
safareli Jun 18, 2017
95eee9b
rename `String.string` to `String.prefix`
safareli Jun 18, 2017
858fda9
fix compiler warnings
safareli Jun 18, 2017
478be1e
fix typo and whitespace char order
safareli Jun 27, 2017
b4dc8ce
update Prefix comment
safareli Jul 12, 2017
902e4db
update prefix variable name
safareli Jul 12, 2017
e8c9bdb
add Lazy List instance for StreamLike
safareli Jul 12, 2017
19e1ed4
move some parsers to String module; take out Stream module
safareli Jul 12, 2017
499c1d0
add m to StreamLike
safareli Jul 30, 2017
9c7e9e9
replace StreamLike to Stream
safareli Jul 30, 2017
5b38fe8
Merge branch 'master' of github.com:purescript-contrib/purescript-par…
safareli Jul 30, 2017
ecb6a3f
resolve ShadowedName position
safareli Jul 30, 2017
ea96e73
use correct wording in setisfy
safareli Jul 30, 2017
61d6317
Avoids closure in Stream class
safareli Dec 3, 2017
13d4bf1
Merge branch 'master' into string
safareli Dec 3, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ install:
- bower install
script:
- npm run -s build
- npm run -s test
after_success:
- >-
test $TRAVIS_TAG &&
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"purescript-foldable-traversable": "^3.0.0",
"purescript-identity": "^3.0.0",
"purescript-integers": "^3.0.0",
"purescript-lists": "^4.0.0",
"purescript-lists": "^4.6.0",
"purescript-maybe": "^3.0.0",
"purescript-strings": "^3.0.0",
"purescript-transformers": "^3.0.0",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"private": true,
"scripts": {
"clean": "rimraf output && rimraf .pulp-cache",
"build": "pulp build && pulp test"
"build": "pulp build",
"test": "pulp test"
},
"devDependencies": {
"pulp": "^11.0.0",
Expand Down
17 changes: 9 additions & 8 deletions src/Text/Parsing/Parser/Pos.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Text.Parsing.Parser.Pos where
import Prelude
import Data.Foldable (foldl)
import Data.Newtype (wrap)
import Data.String (split)
import Data.String (toCharArray)

-- | `Position` represents the position of the parser in the input.
-- |
Expand All @@ -27,10 +27,11 @@ initialPos = Position { line: 1, column: 1 }

-- | Updates a `Position` by adding the columns and lines in `String`.
updatePosString :: Position -> String -> Position
updatePosString pos' str = foldl updatePosChar pos' (split (wrap "") str)
where
updatePosChar (Position pos) c = case c of
"\n" -> Position { line: pos.line + 1, column: 1 }
"\r" -> Position { line: pos.line + 1, column: 1 }
"\t" -> Position { line: pos.line, column: pos.column + 8 - ((pos.column - 1) `mod` 8) }
_ -> Position { line: pos.line, column: pos.column + 1 }
updatePosString pos' str = foldl updatePosChar pos' (toCharArray str)

updatePosChar :: Position -> Char -> Position
updatePosChar (Position pos) c = case c of
'\n' -> Position { line: pos.line + 1, column: 1 }
'\r' -> Position { line: pos.line + 1, column: 1 }
'\t' -> Position { line: pos.line, column: pos.column + 8 - ((pos.column - 1) `mod` 8) }
_ -> Position { line: pos.line, column: pos.column + 1 }
129 changes: 79 additions & 50 deletions src/Text/Parsing/Parser/String.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,89 +2,118 @@

module Text.Parsing.Parser.String where

import Data.Array (many, toUnfoldable)
import Data.Foldable (fold, elem, notElem)
import Data.List as L
import Data.Monoid (class Monoid)
import Data.Monoid.Endo (Endo(..))
import Data.Maybe (Maybe(..))
import Data.Newtype (class Newtype, unwrap)
import Data.String as S
import Data.Unfoldable (class Unfoldable)
import Control.Monad.State (modify, gets)
import Data.Array (many)
import Data.Foldable (elem, notElem)
import Data.Maybe (Maybe(..))
import Data.Newtype (wrap)
import Data.String (Pattern, fromCharArray, length, singleton)
import Text.Parsing.Parser (ParseState(..), ParserT, fail)
import Text.Parsing.Parser.Combinators (try, (<?>))
import Text.Parsing.Parser.Pos (updatePosString)
import Text.Parsing.Parser.Pos (Position, updatePosString, updatePosChar)
import Prelude hiding (between)

-- | A newtype used in cases where there is a prefix to be droped.
newtype Prefix a = Prefix a

derive instance eqPrefix :: Eq a => Eq (Prefix a)
derive instance ordPrefix :: Ord a => Ord (Prefix a)
derive instance newtypePrefix :: Newtype (Prefix a) _

instance showPrefix :: Show a => Show (Prefix a) where
show (Prefix s) = "(Prefix " <> show s <> ")"

class HasUpdatePosition a where
updatePos :: Position -> a -> Position

instance stringHasUpdatePosition :: HasUpdatePosition String where
updatePos = updatePosString

instance charHasUpdatePosition :: HasUpdatePosition Char where
updatePos = updatePosChar

-- | This class exists to abstract over streams which support the string-like
-- | operations which this modules needs.
class StringLike s where
drop :: Int -> s -> s
indexOf :: Pattern -> s -> Maybe Int
null :: s -> Boolean
uncons :: s -> Maybe { head :: Char, tail :: s }

instance stringLikeString :: StringLike String where
uncons = S.uncons
drop = S.drop
indexOf = S.indexOf
null = S.null

-- | Match end-of-file.
eof :: forall s m. StringLike s => Monad m => ParserT s m Unit
-- |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this description is outdated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we fine with description and the law?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine, yeah.

-- | Instances must satisfy the following laws:
-- |
class StreamLike f c | f -> c where
uncons :: f -> Maybe { head :: c, tail :: f, updatePos :: Position -> Position }
drop :: Prefix f -> f -> Maybe { rest :: f, updatePos :: Position -> Position }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we fine with this name? stipPrefix could be used instead too


instance stringStreamLike :: StreamLike String Char where
uncons f = S.uncons f <#> \({ head, tail}) ->
{ head, tail, updatePos: (_ `updatePos` head)}
drop (Prefix p) s = S.stripPrefix (S.Pattern p) s <#> \rest ->
{ rest, updatePos: (_ `updatePos` p)}

instance listcharStreamLike :: (Eq a, HasUpdatePosition a) => StreamLike (L.List a) a where
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name of this instance shouldn't mention char.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, can you please add a lazy list instance too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do once this is merged purescript/purescript-lists#126

uncons f = L.uncons f <#> \({ head, tail}) ->
{ head, tail, updatePos: (_ `updatePos` head)}
drop (Prefix p) s = L.stripPrefix (L.Pattern p) s <#> \rest ->
{ rest, updatePos: unwrap (fold (p <#> (flip updatePos >>> Endo)))}

-- | Match end of stream.
eof :: forall f c m. StreamLike f c => Monad m => ParserT f m Unit
eof = do
input <- gets \(ParseState input _ _) -> input
unless (null input) (fail "Expected EOF")
case uncons input of
Nothing -> pure unit
_ -> fail "Expected EOF"

-- | Match the specified string.
string :: forall s m. StringLike s => Monad m => String -> ParserT s m String
-- | Match the specified stream.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we fine wis descriptions?
We can use function names from Token instead of char and anyChar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that's better. So maybe prefix or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok will use:

  • match for char
  • token for anyChar
  • prefix for string

Will also rename drop from StreamLike to stipPrefix so it lines up with prefix.

string :: forall f c m. StreamLike f c => Show f => Monad m => f -> ParserT f m f
string str = do
input <- gets \(ParseState input _ _) -> input
case indexOf (wrap str) input of
Just 0 -> do
case drop (Prefix str) input of
Just {rest, updatePos} -> do
modify \(ParseState _ position _) ->
ParseState (drop (length str) input)
(updatePosString position str)
true
ParseState rest (updatePos position) true
pure str
_ -> fail ("Expected " <> show str)

-- | Match any character.
anyChar :: forall s m. StringLike s => Monad m => ParserT s m Char
-- | Match any token.
anyChar :: forall f c m. StreamLike f c => Monad m => ParserT f m c
anyChar = do
input <- gets \(ParseState input _ _) -> input
case uncons input of
Nothing -> fail "Unexpected EOF"
Just { head, tail } -> do
Just ({ head, updatePos, tail }) -> do
modify \(ParseState _ position _) ->
ParseState tail
(updatePosString position (singleton head))
true
ParseState tail (updatePos position) true
pure head

-- | Match a character satisfying the specified predicate.
satisfy :: forall s m. StringLike s => Monad m => (Char -> Boolean) -> ParserT s m Char
-- | Match a token satisfying the specified predicate.
satisfy :: forall f c m. StreamLike f c => Show c => Monad m => (c -> Boolean) -> ParserT f m c
satisfy f = try do
c <- anyChar
if f c then pure c
else fail $ "Character '" <> singleton c <> "' did not satisfy predicate"
else fail $ "Character " <> show c <> " did not satisfy predicate"

-- | Match the specified character
char :: forall s m. StringLike s => Monad m => Char -> ParserT s m Char
-- | Match the specified token
char :: forall f c m. StreamLike f c => Eq c => Show c => Monad m => c -> ParserT f m c
char c = satisfy (_ == c) <?> show c

-- | Match a whitespace character.
whiteSpace :: forall s m. StringLike s => Monad m => ParserT s m String
whiteSpace = do
cs <- many $ satisfy \c -> c == '\n' || c == '\r' || c == ' ' || c == '\t'
pure $ fromCharArray cs
-- | Match many whitespace character in some Unfoldable.
whiteSpace :: forall f m g. StreamLike f Char => Unfoldable g => Monoid f => Monad m => ParserT f m (g Char)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you fine with the signature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this function as it's still braking change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remind me again why we'd need to remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are operating on list of some tokens you most likely are not gonna use it.

Major use case of this would be to get String as result, but String is not Unfoldable, so you would still need to map over it with stringFromChars.

I think we can just returning Array Char is fine, and if client wants a string they can map over it (as they would need to do it any ways).

If you agree i would remove this function and rename whitespace' to whitespace (this way we wouldn't have two whitespace functions)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, thanks!

whiteSpace = map toUnfoldable whiteSpace'

-- | Match a whitespace characters but returns them using Array.
whiteSpace' :: forall f m. StreamLike f Char => Monad m => ParserT f m (Array Char)
whiteSpace' = many $ satisfy \c -> c == '\n' || c == '\r' || c == ' ' || c == '\t'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure Data.Array.many is fine here? maybe we should use catenable list or something similar.


-- | Skip whitespace characters.
skipSpaces :: forall s m. StringLike s => Monad m => ParserT s m Unit
skipSpaces = void whiteSpace
skipSpaces :: forall f m. StreamLike f Char => Monad m => ParserT f m Unit
skipSpaces = void whiteSpace'

-- | Match one of the characters in the array.
oneOf :: forall s m. StringLike s => Monad m => Array Char -> ParserT s m Char
-- | Match one of the tokens in the array.
oneOf :: forall f c m. StreamLike f c => Show c => Eq c => Monad m => Array c -> ParserT f m c
oneOf ss = satisfy (flip elem ss) <?> ("one of " <> show ss)

-- | Match any character not in the array.
noneOf :: forall s m. StringLike s => Monad m => Array Char -> ParserT s m Char
-- | Match any token not in the array.
noneOf :: forall f c m. StreamLike f c => Show c => Eq c => Monad m => Array c -> ParserT f m c
noneOf ss = satisfy (flip notElem ss) <?> ("none of " <> show ss)
27 changes: 1 addition & 26 deletions src/Text/Parsing/Parser/Token.purs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
-- | Functions for working with streams of tokens.

module Text.Parsing.Parser.Token
( token
, when
, match
, LanguageDef
( LanguageDef
, GenLanguageDef(LanguageDef)
, unGenLanguageDef
, TokenParser
Expand Down Expand Up @@ -44,28 +41,6 @@ import Text.Parsing.Parser.Pos (Position)
import Text.Parsing.Parser.String (satisfy, oneOf, noneOf, string, char)
import Prelude hiding (when,between)

-- | Create a parser which Returns the first token in the stream.
token :: forall m a. Monad m => (a -> Position) -> ParserT (List a) m a
token tokpos = do
input <- gets \(ParseState input _ _) -> input
case List.uncons input of
Nothing -> fail "Unexpected EOF"
Just { head, tail } -> do
modify \(ParseState _ position _) ->
ParseState tail (tokpos head) true
pure head

-- | Create a parser which matches any token satisfying the predicate.
when :: forall m a. Monad m => (a -> Position) -> (a -> Boolean) -> ParserT (List a) m a
when tokpos f = try $ do
a <- token tokpos
guard $ f a
pure a

-- | Match the specified token at the head of the stream.
match :: forall a m. Monad m => Eq a => (a -> Position) -> a -> ParserT (List a) m a
match tokpos tok = when tokpos (_ == tok)

type LanguageDef = GenLanguageDef String Identity

-- | The `GenLanguageDef` type is a record that contains all parameterizable
Expand Down
20 changes: 11 additions & 9 deletions test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import Text.Parsing.Parser.Combinators (endBy1, sepBy1, optionMaybe, try, chainl
import Text.Parsing.Parser.Expr (Assoc(..), Operator(..), buildExprParser)
import Text.Parsing.Parser.Language (javaStyle, haskellStyle, haskellDef)
import Text.Parsing.Parser.Pos (Position(..), initialPos)
import Text.Parsing.Parser.String (eof, string, char, satisfy, anyChar)
import Text.Parsing.Parser.Token (TokenParser, match, when, token, makeTokenParser)
import Text.Parsing.Parser.String (eof, string, char, satisfy, anyChar, class HasUpdatePosition)
import Text.Parsing.Parser.Token (TokenParser, makeTokenParser)
import Prelude hiding (between,when)

parens :: forall m a. Monad m => ParserT String m a -> ParserT String m a
Expand Down Expand Up @@ -83,6 +83,9 @@ instance testTokensEq :: Eq TestToken where
eq B B = true
eq _ _ = false

instance stringHasUpdatePosition :: HasUpdatePosition TestToken where
updatePos (Position { column, line }) tok = Position { column: column + 1, line}

isA :: TestToken -> Boolean
isA A = true
isA _ = false
Expand Down Expand Up @@ -438,15 +441,14 @@ main = do
parseTest "1*2+3/4-5" (-3) exprTest
parseTest "ab?" "ab" manySatisfyTest

let tokpos = const initialPos
parseTest (fromFoldable [A, B]) A (token tokpos)
parseTest (fromFoldable [B, A]) B (token tokpos)
parseTest (fromFoldable [A, B]) A (anyChar)
parseTest (fromFoldable [B, A]) B (anyChar)

parseTest (fromFoldable [A, B]) A (when tokpos isA)
parseTest (fromFoldable [A, B]) A (satisfy isA)

parseTest (fromFoldable [A]) A (match tokpos A)
parseTest (fromFoldable [B]) B (match tokpos B)
parseTest (fromFoldable [A, B]) A (match tokpos A)
parseTest (fromFoldable [A]) A (char A)
parseTest (fromFoldable [B]) B (char B)
parseTest (fromFoldable [A, B]) A (char A)

parseErrorTestPosition (string "abc") "bcd" (Position { column: 1, line: 1 })
parseErrorTestPosition (string "abc" *> eof) "abcdefg" (Position { column: 4, line: 1 })
Expand Down