Arc Forumnew | comments | leaders | submitlogin
New ppr function
7 points by shader 3049 days ago | 23 comments
I have completed, as far as I can tell, my reimplementation of pg's ppr function. The new version, while longer, is also significantly better than the old version, and is highly recommended if you use the 'src function in Anarki.

Since I haven't gotten a distribution site set up on my server yet like CatDancer, it's just available on github right now. I might be able to put it on Anarki, but I'm worried that I'll mess something up with rntz's fancy new system over there. Location: http://github.com/shader/ppr/tree/master

To get it, just download the raw ppr.arc file into your lib folder, and replace the line in libs.arc that loads pprint.arc with one that loads lib/ppr.arc.

Features:

- It supports arc syntax such as quote, unquote, quasiquote, unquote-splicing, make-br-fn, and docstrings.

- It properly indents nearly all arc forms, and it is easy to add more special cases or change current ones if you don't like the way it's indenting by making an entry in the indent-rules* table.

Caveats:

- It redefines 'len to work with dotted lists. The new version is fully compatible with the old one, but you should be aware of the change.

- It only works with Anarki due to using the new multi-arg make-br-fn, and the $ macro.

Have fun! 'src now looks a lot better. Try it out whenever you're wondering how a particular function or macro works.



2 points by rntz 3048 days ago | link

Hm. It doesn't seem to handle 'fn correctly.

    arc> src.indent-with
    (from "../anarki/lib/ppr.arc")
    (def indent-with (l)
      (fn (xs (o col 0))
          (pr "(")
          (indent-pairs car.xs (+ col 3 l))
          (pr ")")
          (indent-block cdr.xs (+ col 3))))t
'fn is usually indented as follows, as if it were a macro:

    (fn (xs (o col 0))
      (pr "(")
      (indent-pairs car.xs (+ col 3 l))
      (pr ")")
      (indent-block cdr.x (+ col 3))))
A simple fix is to add 'fn to 'indent-rules*:

    --- ../anarki/lib/ppr.arc       2009-06-16 15:42:12.000000000 -0400
    +++ ppr.arc     2009-06-16 20:15:39.000000000 -0400
    @@ -133,7 +133,8 @@
            or      ,[indent-basic _ 2 _2]
            nor     ,[indent-basic _ 3 _2]
            case    ,(indent-case 1)
    -       caselet ,(indent-case 2))))
    +       caselet ,(indent-case 2)
    +       fn      ,[indent-mac _ 2 1 _2])))

     (def ppr (x (o col 0) (o noindent nil))
       " Pretty print. This function displays arc code with proper

-----

1 point by shader 3048 days ago | link

You edited your post before I could respond ;)

But yes, that is exactly the fix, and also why I wrote it that way. Unfortunately, I'm beginning to think that the order of the function arguments could have been planned a bit better. All of those numbers can be rather confusing.

I should probably comment my code too ;)

-----

1 point by rntz 3007 days ago | link

Another bug: 'indent-pairs can't handle being presented with a list containing only one element. For an example in the wild, see the source of 'rand-choice:

    arc> src.rand-choice
    (from "arc.arc")
    (mac rand-choice exprs
      `(case (rand ,(len exprs))
         Error: "car: expects argument of type <pair>; given 1"
More succinctly:

    arc> (ppr '(case foo ,@(bar baz quux xyzzy plugh very long)))
    (case foo
      Error: "car: expects argument of type <pair>; given 1"
The error lies in 'indent-pairs:

    (def indent-pairs (xs (o col 0))
      (let l (apply max (map len:tostring:print:car (keep [cdr _] pair.xs)))
        (on x pair.xs
            (if (~is index 0)
                (do (prn)
                    (sp col)))
            (let str (tostring:print car.x)
              (if cdr.x
                  (do pr.str
                      (sp:- l len.str -1)
                      (ppr cadr.x (+ col 1 l) t))
                  (do (sp (+ l 1)) ;offending expression
                      pr.str))))))
If 'xs is a list with only one element, then (pair xs) returns a list whose sole element is itself a list of one element. 'l is then the result of applying 'max to an empty list, which is nil. (+ l 1) then becomes (+ nil 1), which errors. One simple solution is to add a preceding 0 to the application of max.

    --- ppr-old.arc 2009-07-28 21:10:49.000000000 +0200
    +++ ppr.arc     2009-07-28 21:10:52.000000000 +0200
    @@ -54 +54 @@
    -  (let l (apply max (map len:tostring:print:car (keep [cdr _] pair.xs)))
    +  (let l (apply max 0 (map len:tostring:print:car (keep [cdr _] pair.xs)))

-----

1 point by rntz 3047 days ago | link

It doesn't handle use of complex expressions in functional position very well - no matter how long they are, it prints them on one line. An example is the source code for 'each:

    arc> src.each
    (from "arc.arc")
    (mac each (var expr . body)
      (w/uniq (gseq gf gv)
        `(let ,gseq ,expr
           (if (alist ,gseq)
                 ((rfn ,gf (,gv) (when (acons ,gv) (let ,var (car ,gv) ,@body) (,gf (cdr ,gv)))) ,gseq)
               (isa ,gseq 'table)
                 (maptable (fn ,var ,@body) ,gseq)
               (for ,gv 0 (- (len ,gseq) 1)
                 (let ,var (,gseq ,gv) ,@body))))))t
Notice the long line that sticks out.

-----

1 point by shader 3047 days ago | link

Hmmm. I guess I'll need to add code to support indentation of lists in function position. It should only be one or two lines.

What does pg's ppr do for src.each?

-----

1 point by shader 3047 days ago | link

Ok, apparently pg's version also does the really long line there.

I'll add a special case in ppr for 'cons in function position. How should it be indented? I'll start with two spaces, but I'm open for suggestions.

-----

1 point by shader 3047 days ago | link

Nevermind. I'm indenting it just like 'each is indented in arc.arc.

The new version should now be on github and anarki's master branch.

I'll try and move it over to arc3.master soon.

-----

1 point by shader 3016 days ago | link

There's an interesting bug with my len redefinition, and I was wondering if you guys might have some ideas.

Sometimes when I call len, after the new ppr has been automatically loaded by libs.arc, it produces an error. I have determined that was caused by the fact that len was only partially redefined. Apparently, the one recursive call to len inside the redefinition can sometimes refer to the old len. It doesn't seem to happen when I run the code from the repl.

Any ideas why that happens? Any suggestions for fixing it?

-----

1 point by conanite 3015 days ago | link

Can you post an example?

-----

1 point by rntz 3041 days ago | link

It doesn't handle large arguments to macros correctly.

    arc> (ppr '(let ex (afn (args)
                         (if (no (cdr args)) (car args)
                             `(if (is ,var ,(car args)) ,(cadr args) 
                                  ,(self (cddr args)))))))
    (let ex (afn (args) (if (no (cdr args)) (car args) `(if (is ,var ,(car args)) ,(cadr args) ,(self (cddr args))))))t
This example is lifted from the source of 'caselet.

-----

1 point by rntz 3041 days ago | link

It also doesn't handle long '[]s correctly.

    arc> (ppr '[+ 2 _ a very long bracket fn causes extra parens])
    [(+ 2 _ a very long bracket fn causes extra parens)]t
Your ppr has a rather significant number of bugs it seems.

-----

1 point by shader 3040 days ago | link

well, it is a complicated function. At the very least, it is better than the old version.

That bug must have creeped in because it calls ppr again on the body if it needs to wrap. I'm not 100% sure how to fix it though. Maybe print the first and then map ppr over the rest?

The question is, how to get br-fns to properly indent their bodies. Maybe ppr needs an option to avoid printing the parens.

-----

1 point by shader 3035 days ago | link

I've fixed the bug, and it has been pushed to github but not to arc master. I'm going to try and port it to arc3 first.

-----

1 point by shader 3049 days ago | link

If you got the file in the first few minutes, there was a bug which is now fixed. Just pull/download the new copy and it should work.

Should I go ahead and add it to anarki?

-----

1 point by rntz 3049 days ago | link

Feel free to push it to anarki's master branch, which is for the moment still arc2-based. I haven't yet ported the extended make-br-fn form to arc3, so I'll have to do that before I can forward-port this to arc3.

-----

1 point by shader 3049 days ago | link

Ok, done.

Which branch should I be working on if I want to port things to arc3? So that we can eventually move Anarki's master branch over to arc3? is it arc3.master? arc3.extras? You seem to have a separate branch for the help stuff. Is that just temporary while you try and get it to work?

-----

1 point by rntz 3049 days ago | link

http://arclanguage.org/item?id=9777 explains the details. In brief, hacks should be made relative to the official branch, and then tagged appropriately and merged into arc3.master when they're finished.

The branch for help is not really temporary. I already have 'help, 'src, etc. working. It's there because it's a pretty large subsystem and it's useful to have a branch to track changes.

-----

1 point by shader 3049 days ago | link

I'm not 100% sure what that means. I understood that I should co the origin/official branch, make the code I'm porting work with that, but I don't get what I'm supposed to do next. Make a commit, tag it, and merge it over? stash the changes, co arc3.master, apply the stash, make a commit and tag it?

As for the help branch, am I right in understanding that it's just for keeping track of the development of the help tools, and that master also contains all of the changes?

-----

1 point by rntz 3049 days ago | link

Checkout official (`git checkout official`). Now, switch to a temporary branch (`git checkout -b tmp`). Make your changes. Check that everything works. Commit the changes (`git add ...; git commit -m "..."`). Tag it (`git tag shader.ppr.0`). Then checkout arc3.master and merge in your tag (`git checkout arc3.master; git merge shader.ppr.0`). (At this point the temporary branch can be deleted.)

That's the simple way to do it, anyway. You can in fact make changes which are "relative to" official even if you actually do the work in a temporary branch derived from arc3.master, by using git rebase or git cherry-pick, and I'd highly recommend learning how to use these and other advanced git commands. Also, if you want or need other hacks, you can merge them in after switching to a temporary branch but before making your changes.

And yes, the arc3.master branch does contain all the changes in the arc3.help branch; whenever I change the help branch I merge it into the master branch as well. Note, however, that the reverse is not true - the help branch contains only the modifications needed to get the help system working, and not the other changes in the arc3.master branch.

-----

1 point by shader 3049 days ago | link

Does git not check out tags unless they are requested? Otherwise wouldn't all of those changes stay on the official branch as well? Though I suppose the official branch won't be pushed to by most people, it would still clutter the local version, wouldn't it?

-----

1 point by rntz 3049 days ago | link

Er, whoops, I missed a bit. You are correct; you should make a temporary branch before committing any changes so that official is not modified.

-----

1 point by CatDancer 3048 days ago | link

Just out of curiosity, and I haven't used 'src since I'm not on Anarki, but for that particular application, I was wondering if it would be useful to show the actual source of the function or macro, as it appears in the original source file?

-----

1 point by shader 3047 days ago | link

'src is very helpful, I use it all the time.

However, using the code from the source file is not always possible as you may have typed it in from the repl, or that file may have changed.

Also, many people may not actually indent their code properly, and 'src presumably should provide some sort of standardization.

The only reason that I can think of for reading the text directly from the source file would be that you would get all of the comments as well, not just the docstrings.

So far the ppr function indents the code surprisingly well, and I've actually used it sometimes to figure out how I should be indenting my code ;)

-----