Skip to content

Commit 7c1cc09

Browse files
committed
First release
0 parents  commit 7c1cc09

File tree

9 files changed

+667
-0
lines changed

9 files changed

+667
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/bower_components/
2+
/node_modules/
3+
/.pulp-cache/
4+
/output/
5+
/generated-docs/
6+
/.psc*
7+
/.purs*
8+
/.psa*

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright (c) 2018, Evgeny Pervushin <eapunkxyz@gmail.com>
2+
3+
Permission to use, copy, modify, and/or distribute this software for any
4+
purpose with or without fee is hereby granted, provided that the above
5+
copyright notice and this permission notice appear in all copies.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# purescript-enterspaces
2+
3+
Pretty debug trace.
4+
5+
See [web app](https://github.com/eapunk/enterspaces).
6+
7+
Build js
8+
```sh
9+
pulp browserify --optimise --main Text.Show.Pretty --standalone Enterspaces | uglifyjs -c > enterspaces.min.js
10+
```

bower.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "purescript-enterspaces",
3+
"authors": [
4+
"Evgeny Pervushin <eapunkxyz@gmail.com>"
5+
],
6+
"description": "Pretty debug trace",
7+
"license": "ISC",
8+
"homepage": "https://github.com/eapunk/purescript-enterspaces",
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/eapunk/purescript-enterspaces.git"
12+
},
13+
"ignore": [
14+
"**/.*",
15+
"node_modules",
16+
"bower_components",
17+
"test",
18+
"tests"
19+
],
20+
"dependencies": {
21+
"purescript-prelude": "^3.1.1",
22+
"purescript-parsing": "^4.3.1"
23+
},
24+
"devDependencies": {
25+
"purescript-assert": "^3.0.0"
26+
}
27+
}

src/Text/Parsing/Parser/Json.purs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
-- | Parsers for JSON.
2+
3+
module Text.Parsing.Parser.Json where
4+
5+
import Prelude
6+
import Control.Alt ((<|>))
7+
import Data.Char as C
8+
import Data.Char.Unicode as U
9+
import Data.Int as Int
10+
import Data.List as L
11+
import Data.List.Lazy as LL
12+
import Data.Maybe (Maybe(..))
13+
import Data.String as Str
14+
import Text.Parsing.Parser (ParserT, fail)
15+
import Text.Parsing.Parser.String (class StringLike, char, satisfy)
16+
17+
-- | Parser for JSON string.
18+
jsonString :: forall s m. StringLike s => Monad m => ParserT s m String
19+
jsonString = do
20+
cs <- char '"' *> L.many jsonStringChar <* char '"'
21+
pure $ Str.fromCharArray $ L.toUnfoldable cs
22+
23+
jsonStringChar :: forall s m. StringLike s => Monad m => ParserT s m Char
24+
jsonStringChar = (satisfy \c -> c /= '"' && c /= '\\') <|> jsonEscapedChar
25+
26+
jsonEscapedChar :: forall s m. StringLike s => Monad m => ParserT s m Char
27+
jsonEscapedChar =
28+
char '\\' >>= \_ ->
29+
char '\\'
30+
<|> char '"'
31+
<|> char '/'
32+
<|> char 'b' $> '\b'
33+
<|> char 'f' $> '\f'
34+
<|> char 'n' $> '\n'
35+
<|> char 'r' $> '\r'
36+
<|> char 't' $> '\t'
37+
<|> do
38+
_ <- char 'u'
39+
cs <- LL.replicateM 4 $ satisfy U.isHexDigit
40+
let s = Str.fromCharArray $ LL.toUnfoldable cs
41+
case Int.fromStringAs Int.hexadecimal s of
42+
Just n -> pure $ C.fromCharCode n
43+
Nothing -> fail "Invalid hex string"

src/Text/Show/Pretty.purs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
-- | Pretty debug trace
2+
3+
module Text.Show.Pretty
4+
( prettify
5+
, FoldingOption(..)
6+
) where
7+
8+
import Prelude hiding (between)
9+
import Data.Either
10+
import Data.Foldable (for_)
11+
import Data.List
12+
import Data.List.Lazy as LL
13+
import Data.Maybe
14+
import Data.String as Str
15+
import Data.Tuple
16+
import Control.Alt ((<|>))
17+
import Control.Lazy (fix)
18+
import Text.Parsing.Parser (Parser, runParser)
19+
import Text.Parsing.Parser.Combinators (between, notFollowedBy, sepBy, try)
20+
import Text.Parsing.Parser.Language (haskell)
21+
import Text.Parsing.Parser.String (anyChar, char, satisfy, skipSpaces, string)
22+
import Text.Parsing.Parser.Json (jsonString)
23+
import Control.Monad.RWS
24+
25+
26+
-- | Folding option for show.
27+
data FoldingOption = UnfoldLevel Int | FoldToWidth Int
28+
29+
-- | Parse string and show it with options.
30+
prettify :: {folding :: FoldingOption} -> String -> String
31+
prettify options str = case runParser str exprsParser of
32+
Right expr -> prettyShow options expr
33+
_ -> str
34+
35+
36+
-- | Expresssion data type. Representation of parsed text.
37+
data Expr =
38+
Raw String
39+
| StringExpr String
40+
| CharExpr Char
41+
| ListExpr String String (List (List Expr))
42+
43+
-- | Parser for expressions.
44+
exprsParser :: Parser String (List Expr)
45+
exprsParser =
46+
many $ (try $ fix \p ->
47+
listExpr "{" "}" p
48+
<|> listExpr "[" "]" p
49+
<|> listExpr "(" ")" p
50+
<|> StringExpr <$> (try haskell.stringLiteral <|> jsonString)
51+
<|> CharExpr <$> try haskell.charLiteral
52+
<|> Raw <$> Str.singleton <$>
53+
(notFollowedBy (skipSpaces *> (satisfy \c -> c == '}' || c == ']' || c == ')' || c == ',')) *> anyChar)
54+
) <|> (Raw <$> Str.singleton <$> anyChar)
55+
where
56+
listExpr leftBracket rightBracket p = ListExpr leftBracket rightBracket <$>
57+
between (string leftBracket <* skipSpaces) (skipSpaces *> string rightBracket)
58+
(many p `sepBy` (try $ skipSpaces *> char ',' <* skipSpaces))
59+
60+
61+
type PrintState = {
62+
modeStack :: List (Maybe Mode),
63+
column :: Int,
64+
commitingOneLineMode :: Boolean,
65+
stage' :: Maybe {tokens :: List ShowToken, column :: Int} }
66+
67+
notPrinted :: PrintState
68+
notPrinted = {
69+
modeStack: Nil,
70+
column: 0,
71+
commitingOneLineMode: false,
72+
stage': Nothing }
73+
74+
data Mode = OneLineMode | MultiLineMode
75+
derive instance eqMode :: Eq Mode
76+
77+
data ShowToken = Regular String | OnlyIn Mode String | NewMode | EndMode
78+
derive instance eqShowToken :: Eq ShowToken
79+
80+
-- | Show parsed expressions.
81+
prettyShow :: {folding :: FoldingOption} -> List Expr -> String
82+
prettyShow options exprs =
83+
let Tuple _ w = execRWS (prettyWrite exprs *> commit OneLineMode) options notPrinted
84+
in w
85+
where
86+
prettyWrite :: List Expr -> RWS {folding :: FoldingOption} String PrintState Unit
87+
prettyWrite Nil = pure unit
88+
prettyWrite (expr:restExprs) = do
89+
case expr of
90+
ListExpr leftBracket rightBracket (Nil:Nil) -> write_ leftBracket *> write_ rightBracket
91+
ListExpr leftBracket rightBracket ess -> do
92+
write_ leftBracket
93+
write NewMode
94+
nestingLevel <- getNestingLevel
95+
forL_ ess \{elem: es, isLast} -> do
96+
write $ "\n" `onlyIn` MultiLineMode
97+
void $ LL.replicateM nestingLevel $ write $ " " `onlyIn` MultiLineMode
98+
prettyWrite es
99+
unless isLast do
100+
write_ ","
101+
write $ " " `onlyIn` OneLineMode
102+
write $ " " `onlyIn` MultiLineMode
103+
write_ rightBracket
104+
write EndMode
105+
Raw s -> write_ s
106+
StringExpr s -> do
107+
write_ "\""
108+
write_ $
109+
Str.replaceAll (Str.Pattern "\"") (Str.Replacement "\\\"") $
110+
Str.replaceAll (Str.Pattern "\\") (Str.Replacement "\\\\") s
111+
write_ "\""
112+
CharExpr c -> write_ "'" *> write_ (Str.singleton c) *> write_ "'"
113+
prettyWrite restExprs
114+
115+
onlyIn str mode = OnlyIn mode str
116+
117+
write_ str = write $ Regular str
118+
119+
write token = do
120+
mode <- getMode
121+
let matched = case mode of
122+
Nothing -> true
123+
Just m -> case token of
124+
OnlyIn m' _ -> m == m'
125+
_ -> true
126+
when matched do
127+
state <- get
128+
let str = case token of
129+
Regular s -> s
130+
OnlyIn _ s -> s
131+
_ -> ""
132+
if isNothing mode || isJust state.stage' && str /= "\n"
133+
then do
134+
let engaged = case token of
135+
Regular _ -> true
136+
OnlyIn OneLineMode _ -> true
137+
OnlyIn MultiLineMode _ -> case mode of
138+
Just MultiLineMode -> true
139+
_ -> false
140+
_ -> false
141+
let len = if engaged then Str.length str else 0
142+
let stage = fromMaybe {tokens: Nil, column: state.column} state.stage'
143+
stage2 = stage {tokens = token : stage.tokens, column = stage.column + len}
144+
put state {stage' = Just stage2}
145+
{folding} <- ask
146+
case folding of
147+
FoldToWidth maxWidth ->
148+
when (stage2.column > maxWidth) do
149+
commit MultiLineMode
150+
_ -> pure unit
151+
else do
152+
if str == "\n"
153+
then commit OneLineMode *> modify \st -> st {column = 0}
154+
else modify \st -> st {column = st.column + Str.length str}
155+
tell str
156+
when (token == NewMode) do
157+
modeAfter mode >>= pushMode
158+
when (token == EndMode) do
159+
popMode
160+
161+
modeAfter (Just OneLineMode) = pure $ Just OneLineMode
162+
modeAfter (Just MultiLineMode) = do
163+
{folding} <- ask
164+
case folding of
165+
UnfoldLevel maxLevel -> do
166+
nestingLevel <- getNestingLevel
167+
pure $ Just if nestingLevel >= maxLevel then OneLineMode else MultiLineMode
168+
FoldToWidth 0 -> pure $ Just MultiLineMode
169+
FoldToWidth _ -> do
170+
{commitingOneLineMode} <- get
171+
pure if commitingOneLineMode then Just OneLineMode else Nothing
172+
modeAfter Nothing = pure Nothing
173+
174+
getNestingLevel = get >>= \st -> pure $ length st.modeStack
175+
176+
getMode = get >>= \st -> pure $ fromMaybe (Just MultiLineMode) $ head st.modeStack
177+
178+
pushMode mode = modify \st -> st {modeStack = mode : st.modeStack}
179+
180+
popMode = do
181+
{modeStack} <- get
182+
let m = fromMaybe {head: (Just MultiLineMode), tail: Nil} (uncons modeStack)
183+
modify \st -> st {modeStack = m.tail}
184+
185+
commit mode = do
186+
{stage'} <- get
187+
case stage' of
188+
Nothing -> pure unit
189+
Just stage -> do
190+
modify \st -> st {modeStack = dropWhile isNothing st.modeStack, stage' = Nothing}
191+
let tokens = reverse stage.tokens
192+
when (mode == OneLineMode) do
193+
modify \st -> st {commitingOneLineMode = true}
194+
pushMode $ Just mode
195+
for_ tokens
196+
write
197+
modify \st -> st {commitingOneLineMode = false}
198+
199+
-- | Traverse with information about position, ignoring the final result.
200+
forL_ :: forall a b m. Applicative m => List a -> ({elem :: a, isLast :: Boolean} -> m b) -> m Unit
201+
forL_ Nil _ = pure unit
202+
forL_ (x:Nil) f = void $ f {elem: x, isLast: true}
203+
forL_ (x:xs) f = f {elem: x, isLast: false} *> forL_ xs f

test/Main.purs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module Test.Main where
2+
3+
import Prelude
4+
import Test.Text.Show.Pretty as Pretty
5+
import Test.Text.Parsing.Parser.Json as Json
6+
7+
main = do
8+
Pretty.main
9+
Json.main

test/Text/Parsing/Parser/Json.purs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module Test.Text.Parsing.Parser.Json where
2+
3+
import Prelude
4+
import Data.Either
5+
import Test.Assert (assert')
6+
import Text.Parsing.Parser (runParser)
7+
import Text.Parsing.Parser.Json
8+
9+
main = do
10+
assert' "jsonString 1A" (runParser "\"\\uD834\\uDD1E\"" jsonString == Right "𝄞")
11+
assert' "jsonString 1a" (runParser "\"\\ud834\\udd1e\"" jsonString == Right "𝄞")
12+
assert' "jsonString 2" (runParser "\"Строка\"" jsonString == Right "Строка")
13+
assert' "jsonString 3" (runParser "\" \\\\ \\\" \\/ \\b \\f \\n \\r \\t \"" jsonString == Right " \\ \" / \b \f \n \r \t ")
14+
assert' "jsonString 4" (runParser "\"\\u04441\"" jsonString == Right "ф1")
15+
assert' "jsonString 100" (isLeft $ runParser "\"\\u044\"" jsonString)
16+
assert' "jsonString 101" (isLeft $ runParser "123" jsonString)
17+
assert' "jsonString 102" (isLeft $ runParser "\"123" jsonString)

0 commit comments

Comments
 (0)