I want to pretty print an AST using Haskell and (currently) wl-pprint-annotated (willing to switch to a different library).
How can I make the renderer prefer breaking the softlines of the outer group over the softlines of the inner group?
Minimal Example
Take for example the tuple ((1234, 5678), (abcd, efgh)).
The output I want:
// line width: 10
(
(
1234,
5678
),
(
abcd,
efgh
)
)
// line width: 16
(
(1234, 5678),
(abcd, efgh)
)
// line width: 32
((1234, 5678), (abcd, efgh))
The output I get:
// line width: 10
((1234,
5678),
(abcd,
efgh))
// line width: 16
((1234, 5678), (
abcd, efgh))
// line width: 32
((1234, 5678), (abcd, efgh))
Code:
module Main where
import qualified Prelude
import Prelude hiding((<>))
import Text.PrettyPrint.Annotated.WL
main :: IO ()
main = do
putStrLn $ pp 10
putStrLn $ pp 16
putStrLn $ pp 32
pp w = "// line width: " ++ show w ++ "\n" ++
display (renderPretty 1.0 w doc) ++ "\n"
doc = pair (pair (text "1234") (text "5678"))
(pair (text "abcd") (text "efgh"))
pair x y = group (nest 2 (lparen <//> x <> comma </> y) <//> rparen)
pair x y = group (nest 2 (lparen <##> x <> comma <#> y) <##> rparen)
As ekim found out, I've mixed up </> with <#>
I found the documentation to be confusing, so let me clear it up a little.
First of all the operators </> and <#> are just sugar for line and softline.
See definitions:
x </> y = x <> softline <> y
x <#> y = x <> line <> y
My problem was that I was using softline when what I wanted was line.
Commonalities between line and softline
Both are printed as space when the whole line fits the page. Both are replaced with a line break when the line does not fit the page.
Difference between line and softline
When a group foes not fit the page, all lines of the whole group are replaced with line breaks. That's the behavior I've wanted.
When the line does not fit the page, only the last softline still fitting the page is replaced.
Not the whole group.
It's like the word wrapping in our text editors: Just breaking after the last word that fits to the page.
For example
doc = paragraph p1
paragraph = foldr (</>) mempty . map text . words
p1 = "I want to pretty print an AST using Haskell and (currently) wl-pprint-annotated (willing to switch to a different library)."
is printed as
I want to pretty print an AST using Haskell and
(currently) wl-pprint-annotated (willing to
switch to a different library).
Related
I'm working on a program that receives as input a board game as follows:
#####
#_ ##
# ##
# #
# .#
#####
1 4 (player initial position, marked with '_')
After receiving the input, the program transforms it to a [String].
This case, it would be:
["#####", "#_ ##", "# ##", "# #", "# .#", "#####", "1 4"]
How can I access position [1,4] and transform '_' to 'o'?
Function must return initial list with that transformation.
Very important note: '_' is never displayed on input, I only used it to make clear where position [1,4] is (therefore, on input we only see a blank space, ' ')
Seems like one of those tasks you might have to solve for online coding games. As others pointed out, lists are not really suited for dealing with coordinates like this. However, if you are not able to use better libraries (like in coding games) you will have to do some more work.
Here is the code from my ghci session (transforming to a proper program is left as an exercise for the reader...):
let input = ["#####", "#_ ##", "# ##", "# #", "# .#", "#####", "1 4"]
let reverseInput = reverse input
let position = head reverseInput
let board = tail reverseInput
let posX = read $ takeWhile (/=' ') position :: Int
let posY = read $ takeWhile (/=' ') $ reverse position :: Int
let (unchangedBoard, changedBoard) = splitAt posY board
let (unchangedRow, changedRow) = splitAt posX $ head changedBoard
let newRow = unchangedRow ++ "o" ++ tail changedRow
let newBoard = unchangedBoard ++ [newRow] ++ tail changedBoard
let finalOutput = reverse newBoard
mapM_ putStrLn finalOutput
Also note this code is very brittle as it uses partial functions all over the place (tail, head, read). You could try to use pattern matching instead to make the code more robust.
I am on the lookout for a gsub based function which would enable me to do combinatorial string replacement, so that if I would have an arbitrary number of string replacement rules
replrules=list("<x>"=c(3,5),"<ALK>"=c("hept","oct","non"),"<END>"=c("ane","ene"))
and a target string
string="<x>-methyl<ALK><END>"
it would give me a dataframe with the final string name and the substitutions that were made as in
name x ALK END
3-methylheptane 3 hept ane
5-methylheptane 5 hept ane
3-methyloctane 3 oct ane
5-methyloctane 5 ... ...
3-methylnonane 3
5-methylnonane 5
3-methylheptene 3
5-methylheptene 5
3-methyloctene 3
5-methyloctene 5
3-methylnonene 3
5-methylnonene 5
The target string would be of arbitrary structure, e.g. it could also be string="1-<ALK>anol" or each pattern could occur several times, as in string="<ALK>anedioic acid, di<ALK>yl ester"
What would be the most elegant way to do this kind of thing in R?
How about
d <- do.call(expand.grid, replrules)
d$name <- paste0(d$'<x>', "-", "methyl", d$'<ALK>', d$'<END>')
EDIT
This seems to work (substituting each of these into the strplit)
string = "<x>-methyl<ALK><END>"
string2 = "<x>-ethyl<ALK>acosane"
string3 = "1-<ALK>anol"
Using Richards regex
d <- do.call(expand.grid, list(replrules, stringsAsFactors=FALSE))
names(d) <- gsub("<|>","",names(d))
s <- strsplit(string3, "(<|>)", perl = TRUE)[[1]]
out <- list()
for(i in s) {
out[[i]] <- ifelse (i %in% names(d), d[i], i)
}
d$name <- do.call(paste0, unlist(out, recursive=F))
EDIT
This should work for repeat items
d <- do.call(expand.grid, list(replrules, stringsAsFactors=FALSE))
names(d) <- gsub("<|>","",names(d))
string4 = "<x>-methyl<ALK><END>oate<ALK>"
s <- strsplit(string4, "(<|>)", perl = TRUE)[[1]]
out <- list()
for(i in seq_along(s)) {
out[[i]] <- ifelse (s[i] %in% names(d), d[s[i]], s[i])
}
d$name <- do.call(paste0, unlist(out, recursive=F))
Well, I'm not exactly sure we can even produce a "correct" answer to your question, but hopefully this helps give you some ideas.
Okay, so in s, I just split the string where it might be of most importance. Then g gets the first value in each element of r. Then I constructed a data frame as an example. So then dat is a one row example of how it would look.
> (s <- strsplit(string, "(?<=l|\\>)", perl = TRUE)[[1]])
# [1] "<x>" "-methyl" "<ALK>" "<END>"
> g <- sapply(replrules, "[", 1)
> dat <- data.frame(name = paste(append(g, s[2], after = 1), collapse = ""))
> dat[2:4] <- g
> names(dat)[2:4] <- sapply(strsplit(names(g), "<|>"), "[", -1)
> dat
# name x ALK END
# 1 3-methylheptane 3 hept ane
I'm trying to go through the mentioned chapter. While reading and thinking about exercises i faced several difficulties.
First of all, should not be the signatures of fill and nest functions to be :: Int -> Doc -> String? I assumed that book is correct - they should not.
Next, whether in exercise 1 it is meant that only the out-of-margin lines should not be filled with spaces or entire text should not be processed if at least one such line appears?
The next question is about exercise 2. I almost entirely don't understand what authors meant. There can be two interpretations of what they meant: either we should produce something like
{"foo": 123,
"bar": 456}
meaning that indentation of the first lexeme after opening delimiter (brace or bracket) is remembered and the next lines are indented with that amount of indentation (and then the first nest argument makes no sense), or we should produce (with amount of indentation = 4)
{
"foo": {
"baz": 123
},
"bar": 456
}
but it makes no sense if user forgets to insert line breaks after/before opening/closing delimiters. Or should we force this insertion? Is it possible? (I know it's possible to always insert line breaks, but is it possible to recognize whether user inserted them himself?).
Please also note that i have taken a requirement not to add more data constructors to Doc type.
Exercise 1
Going through the book myself, I was stuck on how to interpret the requirements of the first exercise. After a bit of searching, I hit upon this approach to the problem.
The idea is that every time you encounter a Line, you first pad out the Doc before the Line.
Barry Allison's posted solution seems to have lost some <> characters in HTML formatting, but if I put them back into the places I guess they're supposed to go, his code seems to work.
The drawback of that solution is that it doesn't pad the last line, so I've modified the implementation to do that:
fill :: Int -> Doc -> Doc
fill width x = hcat (init (scanLines 0 [x <> Line]))
where
scanLines _ [] = []
scanLines col (d:ds) =
case d of
Empty -> scanLines col ds
Char c -> Char c : scanLines (col + 1) ds
Text s -> Text s : scanLines (col + length s) ds
Line -> Text (padLine (width - col)) : Line : scanLines 0 ds
a `Concat` b -> scanLines col (a:b:ds)
_ `Union` b -> scanLines col (b:ds)
padLine w = replicate w ' '
In order to pad the last line, the fill function first appends a Line to the input, and then calls scanLines. The difference from Barry Allison's solution is that in this implementation, scanLines has the type Int -> [Doc] -> [Doc]. This means that init can be used to throw away the trailing Line, and hcat can finally turn the [Doc] into a Doc.
The drawback of this solution is that it throws away the flattened version of the any Union.
If you modify padLine to replicate the character '#' instead of ' ', you can see this output:
*PrettyJSON> let value = renderJValue (JObject [("f", JNumber 1), ("q", JBool True)])
*PrettyJSON> putStrLn (pretty 20 (Prettify.fill 30 value))
{"f": 1.0,####################
"q": true#####################
}#############################
It's likely that there's a more idiomatic and elegant solution to this exercise, but I'm posting this based on what I've learned so far from reading the book.
Exercise 2
When it came to the second exercise, I took some liberties with interpreting the requirements. As other people have pointed out, both here and elsewhere, the requirements aren't clear.
The following, then, is more of a sketch of solution:
nest :: Int -> Doc -> Doc
nest indentation x = indent 0 [x]
where
indent _ [] = Empty
indent nestLevel (d:ds) =
case d of
Empty -> indent nestLevel ds
Char '{' -> padLine nestLevel <> Char '{' <> indent (nestLevel + 1) (Line:ds)
Char '}' -> padLine (nestLevel - 1) <> Char '}' <> indent (nestLevel - 1) ds
Char '[' -> padLine nestLevel <> Char '[' <> indent (nestLevel + 1) (Line:ds)
Char ']' -> padLine (nestLevel - 1) <> Char ']' <> indent (nestLevel - 1) ds
Char c -> Char c <> indent nestLevel ds
Text s -> Text s <> indent nestLevel ds
Line -> padLine nestLevel <> indent nestLevel ds
a `Concat` b -> indent nestLevel (a:b:ds)
a `Union` b -> indent nestLevel (a:ds) `Union` indent nestLevel (b:ds)
padLine nl = Line <> Text (replicate (nl * indentation) ' ')
There are most likely boundary cases, and subtle interactions with the pretty function from the book that could mean that this isn't a complete solution, but at this point, I've already used too many hours on this, and I don't feel that I'm learning Haskell from struggling with unclear requirements.
Here's a sample GHCI interaction that demonstrates how it works:
*PrettyJSON> let value = renderJValue (JObject [("foo", (JObject [("baz", JNumber 123)])), ("bar", JNumber 456)])
*PrettyJSON> putStrLn (pretty 10 (Prettify.nest 4 value))
{
"foo":
{
"baz": 123.0
},
"bar": 456.0
}
As you can tell, there are too many line breaks, but the basic indentation seems to produce some sort of decent-looking nesting.
I would like to paste two character strings together and pad at the end with another character to make the combination a certain length. I was wondering if there was an option to paste that one can pass or another trick that I am missing? I can do this in multiple lines by figuring out the length of each and then calling paste with rep(my_pad_character,N) but I would like to do this in one line.
Ex: pad together "hi", and "hello" and pad with an "a" to make the sequence length 10. the result would be "hihelloaaa"
Here is one option:
s1 <- "hi"
s2 <- "hello"
f <- function(x, y, pad = "a", length = 10) {
out <- paste0(x, y)
nc <- nchar(out)
paste0(out, paste(rep(pad, length - nc), collapse = ""))
}
> f(s1, s2)
[1] "hihelloaaa"
You can use the stringr function str_pad
library(stringr)
str_pad(paste0('hi','hello'), side = 'right', width = 10 , pad = 'a')
I really need help in writing this function in Haskell, I don't even know where to start. Here are the specs:
Define a function flagpattern that takes a positive Int value greater than or equal to five and returns a String that can be displayed as the following `flag' pattern of dimension n, e.g.
Main> putStr (flagpattern 7)
#######
## ##
# # # #
# # #
# # # #
## ##
#######
Assuming you want a "X" enclosed in 4 lines, you need to write a function that given a coordinate (x,y) returns what character should be at that position:
coordinate n x y = if i == 0 then 'X' else ' '
(This version outputs only the leftmost X'es, modify it, remember indices start with 0)
Now you want them nicely arranged in a matrix, use a list comprehension, described in the linked text.
You should start from your problem definition:
main :: IO ()
main = putStr . flagPattern $ 7
Then, you should ask yourself about how much dots flag has:
flagPattern :: Int -> String
flagPattern = magic $ [1..numberOfDots]
Then, (hard) part of magic function should decide for each dot whether it is or #:
partOfMagic ...
| ... = "#" -- or maybe even "#\n" in some cases?
| otherwise = " "
Then, you can concatenate parts into one string and get the answer.
Start with the type signature.
flagpattern :: Int -> String
Now break the problem into subproblems. For example, suppose I told you to produce row 2 of a size 7 flag pattern. You would write:
XX XX
Or row 3 of a size 7 flag pattern would be
X X X X
So suppose we had a function that could produce a given row. Then we'd have
flagpattern :: Int -> String
flagpattern size = unlines (??? flagrow ???)
flagrow :: Int -> Int -> String
flagrow row size = ???
unlines takes a list of Strings and turns it into a single String with newlines between each element of the list. See if you can define flagrow, and get it working correctly for any given row and size. Then see if you can use flagrow to define flagpattern.