0

I am attempting to derive a new emacs mode from python.el (the current official gnu one) for Boo and I am having trouble with altering the indentation. Does anyone have any suggestions about how to best handle this? I do not need to change anything drastically, just add some new block forms and stuff.

For example, since this is for Boo, the try/except syntax uses "ensure" instead of "finally". I can change this easily enough in python.el by changing the block-start def of python-rx-constituents. However, I can't seem to be able to override this in a derived mode because python-rx-constituents is being then used by a macro, python-rx, and I guess once those two things are defined when python.el loads (as it has to, since I am deriving from it), I can no longer override it after-load or in a hook? Because I've definitely changed it in memory and in a hook after python.el loads and in an after-load statement and none of them work. While directly altering python.el works fine.

Here is the code in question from python.el:

(eval-when-compile
  (defconst python-rx-constituents
    `((block-start          . ,(rx symbol-start
                                   (or "def" "class" "if" "elif" "else" "try"
                                       "except" "finally" "for" "while" "with"
                                       )
                                   symbol-end))
      (decorator            . ,(rx line-start (* space) ?@ (any letter ?_)
                                   (* (any word ?_))))
      (defun                . ,(rx symbol-start (or "def" "class") symbol-end))
      (if-name-main         . ,(rx line-start "if" (+ space) "__name__"
                                   (+ space) "==" (+ space)
                                   (any ?' ?\") "__main__" (any ?' ?\")
                                   (* space) ?:))
      (symbol-name          . ,(rx (any letter ?_) (* (any word ?_))))
      (open-paren           . ,(rx (or "{" "[" "(")))
      (close-paren          . ,(rx (or "}" "]" ")")))
      (simple-operator      . ,(rx (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%)))
      ;; FIXME: rx should support (not simple-operator).
      (not-simple-operator  . ,(rx
                                (not
                                 (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%))))
      ;; FIXME: Use regexp-opt.
      (operator             . ,(rx (or "+" "-" "/" "&" "^" "~" "|" "*" "<" ">"
                                       "=" "%" "**" "//" "<<" ">>" "<=" "!="
                                       "==" ">=" "is" "not")))
      ;; FIXME: Use regexp-opt.
      (assignment-operator  . ,(rx (or "=" "+=" "-=" "*=" "/=" "//=" "%=" "**="
                                       ">>=" "<<=" "&=" "^=" "|=")))
      (string-delimiter . ,(rx (and
                                ;; Match even number of backslashes.
                                (or (not (any ?\\ ?\' ?\")) point
                                    ;; Quotes might be preceded by a escaped quote.
                                    (and (or (not (any ?\\)) point) ?\\
                                         (* ?\\ ?\\) (any ?\' ?\")))
                                (* ?\\ ?\\)
                                ;; Match single or triple quotes of any kind.
                                (group (or  "\"" "\"\"\"" "'" "'''"))))))
    "Additional Python specific sexps for `python-rx'")

  (defmacro python-rx (&rest regexps)
    "Python mode specialized rx macro.
This variant of `rx' supports common python named REGEXPS."
    (let ((rx-constituents (append python-rx-constituents rx-constituents)))
      (cond ((null regexps)
             (error "No regexp"))
            ((cdr regexps)
             (rx-to-string `(and ,@regexps) t))
            (t
             (rx-to-string (car regexps) t))))))

I would like to change python-rx-constituents so that block-start includes "ensure" instead of finally.

cacti
  • 475
  • 3
  • 15
  • Derived-mode seems not suitable here. Why not bluntly copy the file, store as boo.el, replace the prefix by "boo-", reload and edit the stuff which needs changing? – Andreas Röhler Feb 15 '14 at 19:34
  • That's actually exactly what I did on my first attempt, Andreas, and it works fine. However, I wanted to "clean it up" so that I can release the source for it and I figured deriving from an existing mode would be the better way for copyright and attribution and such. Right now I'm just confused as to why when I override the variables and macros nothing seems to happen. The values are changed but there is no noticeable effect. – cacti Feb 15 '14 at 19:54
  • The actual problem is that during compilation of `python.el` the regular expressions are generated by the `python-rx`-macro and then directly incorporated as string into the source code. The actual indentation is calculated in `python-indent-calculate-indentation`. The status `after-beginning-of-block` is detected in `python-indent-context` and there you have directly the macro call `(python-rx block-start)` within the function definition. The variable definition for `python-rx-constituents` is also encapsulated into `eval-when-compile`. – Tobias Feb 15 '14 at 19:54
  • Tobias, Thanks! What if I don't compile the code but use it interpreted? I thought `eval-when-compile` would translate to `progn`, and the macro could then be overridden later, so that when `python-indent-context` was called it would use the new `python-rx` macro. I guess I am confused on when macros are evaluated. – cacti Feb 15 '14 at 19:58
  • You are right. `progn` at interpretation. Works also with `load-library`. – Tobias Feb 15 '14 at 19:59
  • The bad thing is that the macro call `(python-rx block-start)` is going to be expanded at the time of **compilation** of the function `python-indent-context` (not function definition). That means the function `python-indent-context` does not use `python-rx-constituents` anymore. This is the reason for `python-rx-constituents` being declared as `eval-when-compile`. – Tobias Feb 15 '14 at 20:10
  • Ah ok, this is starting to make sense. So basically I could override the macro but for it to have any effect I would have to re-define all the functions and variables that referenced in previously so that they picked up on the new binding? – cacti Feb 15 '14 at 20:12
  • Meanwhile I tried and tested with `symbol-function`. It turns out that only compilation is critical. Directly loading the `el`-file should work. But, this is not good practice. Often the `el`-files are not part of the binary package. It looks like the easiest way is that one proposed by Andreas Röhler. But, code-copying is bad because you have to maintain much code in your own branch. – Tobias Feb 15 '14 at 20:20
  • Tobias, If you post that I will accept it as an answer. I just tested redefining a few of the indentation functions that use the rx macro after I changed the macro and it seems to work fine so far. Copying those functions beats copying the whole mode. Next step is to see if there is a better way, like advice or something along those lines. Thank you for your help. – cacti Feb 15 '14 at 20:23

2 Answers2

1

As commented already, employing derived-mode is not suitable here: you can't back-change a macro. Also re-defining it isn't recommendable: the order of loading/evaluation than will decide which one is in effect - at a larger scale that means running into a mess.

Copy the file, store as boo.el, replace the prefix by "boo-", reload and edit the stuff which needs changing.

Your concern expressed IMO isn't justified, as permitting copying, changing and re-release of the changed code is the core of the GPL.

Andreas Röhler
  • 4,804
  • 14
  • 18
0

Copying the file is a bad idea, indeed, since it makes it painful to track the evolution of python.el. Deriving is not that great an idea either, since Boo isn't really an extension or a variation of Python, so you probably want to reuse some of its indentation machinery and some of its code to understand the significant-indentation, but not much more than that.

You might like to contact python.el's author (with Cc to emacs-devel) to see if python.el could be adjusted to make your life easier. e.g. maybe extract the common code to an auxiliary file that can be shared between the two. Ideally, this file might be usable for cofferscript-mode and maybe Haskell-mode as well.

Stefan
  • 27,908
  • 4
  • 53
  • 82