Saturday, September 27, 2008

100 Pushups: week 5 (finished)

You'd think I had finished the whole stinking program, but I'm feeling pretty good about finishing week 5 after numerous iterations. I checked my records and I've been doing week 5 since the middle of July.

But I did finish it. So, I may suck, but I don't suck as much as people who *haven't* finished week 5 ever. :)

Any way, on to week 6 now. (For who knows how long).

Wednesday, September 17, 2008

emacs python mode from scratch: stage 6 - python-indentation-levels

OK now let's see if python-indentation-levels works as advertised when it is copied over.

For the following test code block (with cursor at position POINT):

class Foo(object):
def __init__(self, *args, **kwargs):
print "hi"
print 'qwerty' [POINT]

it returns the following list:

((0 . #("class Foo(object):" 0 5 (face font-lock-keyword-face fontified t) 5 6 (fontified t) 6 9 (face font-lock-type-face fontified t) 9 18 (fontified t)))
(4 . #("def __init__(self, *args, **kwargs):" 0 3 (face font-lock-keyword-face fontified t) 3 4 (fontified t) 4 12 (face font-lock-function-name-face fontified t) 12 13 (fontified t) 13 17 (face font-lock-keyword-face fontified t) 17 36 (fontified t)))
(8 . #("print \"hi\"" 0 5 (face font-lock-keyword-face fontified t) 5 6 (fontified t) 6 10 (face font-lock-string-face fontified t))))

This is a list of lists where each internal list consists of a pair of tab position and the object with which it is matching.

So everything seems to be functioning correctly,now we just need to delve into how this works exactly.

Basically the whole function is a cond with three cases:

  • statement following a block open statement
  • comment following a comment
  • everything else

Before we go through these three cases I have to say that the style is very new to me. The "predicate" part for the first two cases of each case branch is a block of code that acts as if what it is testing is true. If it succeeds then it is true and it sets the indent list appropriately, otherwise it tries the next case. It is just an unusual style (to me) to run code in a "if" statement like this. But it obviously works, so I'll just adjust my expectations accordingly.

  • statement following a block open statement

    The logic here is to that check all of the following actions/tests work:

    • move to a previous statement
    • check that it is an opening block statement
    • save the value of the indent
    • move to the end of the statement
    • skip comments and blanks
    • make sure we are at a ":"
    • add the fixed indent amount to the indent of the previous block statement

    I don't really understand all the machinations here.

    Intuitively I would have stopped after the first 3 steps.

    Ahh... now I see. The comments are useful here.

    ;; Check we don't have something like:
    ;; if ...: ...

    So if we go to the end of the statement and don't find a ":" we have the above scenario and the "normal" indenting rule won't work.

  • comment following a comment

    This is more straightforward. If the current line is a comment and the previous line is also a comment, then there is only one choice for indent levels: the indent level of the previous line.

  • all other cases.

    This logic doesn't look as bad as I would have at first suspected.

    The first thing added to the list of indentation levels will be the position of the previous lines indentation *if* the previous line is part of a pair like if/else that makes sense to line up with AND it is not a block closer (e.g. return) that doesn't make sense to line up with

    Next we are going to crawl up a block at a time and collect indentation levels on the way up. We only skip a level if we had a word like "else" and the block we are examining doesn't match our "start" word

    Even if we had nothing we throw a 0 position on the list for good measure and then set a couple global values (python-indent-list and python-indent-list-length)

So that was one of the first "big" functions I've had to work through and it wasn't too bad. It's amazing how many functions were required do something as simple sounding as get suggested indentation levels.

And of course we *still* can't indent. So the next phase will hopefully be using the output of this function to actually navigate using tab. I would never have guessed so much magic was going on when the tab key was hit.

I will need to keep in mind that these values are being set globally and see if I can guess why. As a guess I'd say its because this operation is expensive and while you are on a line there is no need to recalculate it. If that's true I should at some point see some code that recognizes that point has moved to a new line and invalidates the current python-indent-list

We are now 23% of the way through the code (by lines - including comments).

Tuesday, September 9, 2008

emacs python mode from scratch: stage 5 - more movement methods

So let's continue with the seemingly modest goal of getting tabbing to work

Presumably my current target is to get python-indent-line working

From some quick browsing this function has the following important dependencies:

db-python-indent-line-1 ;; which sets global: db-python-indent-list-length
db-python-calculate-indentation (104 lines)

And then looking at python-calculate-indentation we find it requires

- db-python-indent-string-contents # global var
- db-python-indentation-levels # 59 lines

So I guess this time our target is to get the support functions in place for python-indentation-levels.

- python-indent
- python-block-pairs

and the function(s)

- python-first-word
- python-initial-text
- python-beginning-of-block
- python-end-of-block

  • python-indent:

    This is pretty straight forward, set a customizable variable for what the default number of columns for indentation will be. Just to be thorough we need to look up what safe-local-variable means.

    From poking around in files.el

    ;; Safe local variables:
    ;; For variables defined by major modes, the safety declarations can go into
    ;; the major mode's file, since that will be loaded before file variables are
    ;; processed.
    ;; For variables defined by minor modes, put the safety declarations in the
    ;; file defining the minor mode after the defcustom/defvar using an autoload
    ;; cookie, e.g.:
    ;; ;;;###autoload(put 'variable 'safe-local-variable 'stringp)
    ;; Otherwise, when Emacs visits a file specifying that local variable, the
    ;; minor mode file may not be loaded yet.
    ;; For variables defined in the C source code the declaration should go here:

    So basically it's a way to do some simple type checking on a variable.

  • python-block-pairs

    This is an alist of python keywords and the keywords that they are the "closers" for.

  • python-first-word

    A simple function that returns to the beginning of code for a line and calls current-word

  • python-initial-text

    A function to grab the non-comment code on a line. Interestingly this function doesn't seem to work as advertised. Both in my hand copied subset of code and in the full working original both seem to keep the comments at the end of the line.

    I will have to keep this in mind when looking at how it is used to see if this will affect the functionality.

  • python-beginning-of-block

    Move point to beginning of containing block. It starts by moving past any blank space and/or comments and then going to start of current statement.

    Then while point is not on the 0th column and/or the arg passed in is not 0 continue recursively until one of the target conditions is true

    The logic here is a bit of a challenge (for me) to follow. Its a twisty maze of whiles, whens, ands, recursion and throw/catches.

    It seems to be doing something like:

    Move up a line and continue doing it until either we can't go up any more or we hit the condition where we generate a 'done (ie the conditions match a new outer block have been hit).

    This is probably the most "lispy" code so far and seemed excessively weird when I first tried to wrap my brain around it. But it seems halfway sensible to me now

  • python-end-of-block

    For some reason I was expecting this function to be a simple variation of python-beginning-of-block, but the logic is fairly different. It *does* use many of the same function but the way they are combined is not a simple reverse of the above logic.

    This function and the above are definitely a bit more sophisticated in their logic and the code is more strange (to me). As an example:

    within a let* expression there is the following "variable assignent" which is really not a variable assignment.

    (_ (if (db-python-comment-line-p)
    (db-python-skip-comments/blanks t)))

    At this point I'm not sure whether to think this is an ugly hack or just to try and get used to this as idomatic emacslisp. Seems like it would be cleaner just to do this test and function call before the let*, or within a nested let. Hmmm... Can't do it before because we need the value of point before moving and having a nested let makes the code more complicated. I guess I'm already starting to see this more as a clever hack than first blush.

    More strangely, python-beginning-of-block is called recursively and this function is an iteration. I wonder if these were done by different people or in different moods. Or perhaps there is some compelling reason not to do it recursively. Not obvious to me in any case why the difference in style.

Anyway, we are now closer to being able to implement some tab bevhavior. With any luck next phase will have us implementing python-indentation-levels

By lines of code I'm about 20% of the way through.

Sunday, September 7, 2008

100 Pushups: week 5

I have vowed to get to one hundred pushups. And I *will* keep at it well past the point where a sane person would just cut their loss. But in case any one is wondering, I've been on week 5 for about 6 weeks now.


True, there was a week off because I was sick and another following that for a week vacation. And yes, I did lose a lot of progress during those two weeks. But man, this is a non-trivial challenge.

I really have to wonder how many people have followed this (and exclusively this) plan to victory.

Any way, as long as each time I go through week 5 I get a little better then I don't feel *too* bad about being relentless. It's when I plateau that I have to start wondering about my sanity.

For what it's worth I can currently do a max of 40 pushups in one set. 100 stills seems absurdly far off.