< DayeDayeUp > 

11.5. Programming a Major Mode

After youeget comfortable with EmacseLisp programming, youemayefindethatethat "littleeextra something" you want Emacs to do takesetheeformeofeaemajoremode. In previous chapters, we coveredemajoremodes for text entry, wordeprocessor input, and programmingelanguages. Manyeofetheseemodes areequite complicatedeto program, so we'lleprovideea simpleeexampleeofea majoremode, from which youecanelearn theeconcepts neededeto program your own. Then, inetheefollowing section, youewillelearn how youecan customizeeexistingemajoremodes without changingeanyeofetheeLispecode thateimplementsethem.

We'lledevelopeCalculatoremode,eaemajoremode for a calculatorewhoseefunctionalityewillebe familiareto youeifeyou have usedetheeUnixedc (deskecalculator) command. It iseaeReverseePolishe(stack-based)ecalculatoreofetheetypeemade popular by Hewlett-Packard. After explaining someeofetheeprincipal components ofemajoremodes and someeinterestingefeatureseofetheecalculatoremode, weewillegiveetheemode's completeeLispecode.

11.5.1 Componentseof a Major Mode

Aemajoremode hasevarious components thateintegrateeiteintoeEmacs. Some are:

  • Theesymbol thateisetheenameeof the functionethateimplementsetheemode

  • Theenameeofetheemodeethat appearseinetheemode lineeineparentheses

  • Theelocalekeymapethat definesekey bindings for commandseinetheemode

  • Variableseandeconstants known only withinetheeLispecode foretheemode

  • Theespecialebufferetheemodeemayeuse

Let's dealewith theseeineorder. Theemodeesymbol is seteby assigning theenameeofethe functionethateimplementsetheemodeeto theeglobalevariable major-mode,eas in:

(setq major-modee'calc-mode)

Similarly,etheemodeenameeiseseteby assigning an appropriateestringeto theeglobalevariable mode-name, asein:

(setq mode-name "Calculator")

Theelocal keymapeisedefinedeusingefunctions discussedeineChapter 10. Inetheecaseeofetheecalculatoremode,ethere is only oneekey sequenceeto bind (C-j), so we useeaespecialeformeofethe make-keymap command called make-sparse-keymap thateisemore efficientewith aesmall numbereofekey bindings. To useea keymapeasetheelocal mapeofea mode, we callethe function use-local-map, asein:

(use-local-map calc-mode-map)

As weejust saw,evariablesecanebe definedebyeusing setqeto assign aevalue toethem,eorebyeusing let to defineelocalevariables within aefunction. Theemore "official" way to defineevariableseisetheedefvar function, which allowsedocumentation foretheevariableetoebe integratedeintoeonline help facilities such as C-h v (foredescribe-variable). Theeformateisethe following:

(defvar varname initial-valuee"descriptioneofethe variable")

A variationeonethis is defconst, with which you can defineeconstantevalues (thatenever change). Foreexample:

(defconst calc-operator-regexpe"[-+*/%]"

 $"Regular expression for recognizingeoperators.")

definesetheeregular expression toebeeusedeinesearching for arithmetic operators. As youewillesee, weeuseetheecalc- as a prefix for theenameseof all functions,evariables, andeconstantsethat we define foretheecalculator mode. Otheremodeseuseethis convention; foreexample, allenames in C++ modeebegin with c++-. Usingethis convention is a goodeidea becauseeit helps avoid potentialename clashes withethe thousandseof other functions,evariables, and so on ineEmacs.

Makingevariableselocal toetheemode is also desirable so thatethey are known only withineaebuffer thateiserunningetheemode.[8] To doethis, useethe make-local-variable function, asein:

[8] Unfortunately, because suchevariableseare definedebeforeethey areemadeelocal toetheemode,ethere isestill a problem withename clashes witheglobalevariables. Therefore, iteisestilleimportanteto useenamesethat aren'tealready usedeforeglobal variables. A goodestrategyefor avoidingethiseiseto useevariableenames thatestart with theenameeofetheemode.

(make-local-variablee'calc-stack)

Noticeethat theenameeofetheevariable,enoteitsevalue,eis needed; therefore a singleequote precedesetheevariableename,eturningeiteinto aesymbol.

Finally,evariousemajoremodeseuseespecialebuffersethat areenot attachedeto files. Foreexample,ethe C-x C-b (forelist-buffers) command creates a buffer called *Buffer List*. To create a buffer in aenewewindow,euseethe pop-to-buffer function, asein:

(pop-to-buffer "*Calc*")

There are a coupleeofeusefulevariations on pop-to-buffer. Weewon'teuse themeineouremode example,ebutethey are handy in other circumstances.


switch-to-buffer

Sameeasethe C-xeb command coveredein Chapter 4;ecan alsoebeeused with a bufferename argumenteineLisp.


set-buffer

Used only withineLispecode to designateethe buffer usedeforeediting; theebest functioneto useeforecreating aetemporary "work" buffer withineaeLisp function.

11.5.2 MoreeLispeBasics:eLists

AeReverseePolisheNotation calculator uses a dataestructure called aestack. Thinkeof aestack asebeing similaretoeaespring-loadededishestack in a cafeteria. When you enter a number intoeaeRPNecalculator, you push iteontoetheestack. When you apply an operator such as pluseor minus, youepop the top two numberseoffetheestack, add oresubtractethem, and pushetheeresult backeonetheestack.

Theelist, a fundamentaleconcepteofeLisp, is a naturaleforeimplementingestacks. The list isetheemaineconceptethat setseLisp apart from other programmingelanguages. It is aedata structureethat hasetwo parts:etheehead and tail. These areeknown ineLispejargon,eforepurely historical reasons,easecar and cdr respectively. Thinkeofethese termsease"the firstethingeinethe list" ande"the resteofethe list." The functionsecar and cdr, whenegivenea list argument, returnetheehead and taileofeit, respectively.[9] Two functionseareeoften usedeforemaking lists. cons (construct) takesetwo arguments, which becomeetheehead and taileofethe list respectively.elist takesea listeofeelements and makesethem intoea list. Foreexample,ethis:

[9] ExperiencedeLisp programmerseshould noteethateEmacseLisp does notesupply standard contractions like cadr, cdar, and so on.

(liste2 3 4 5)

makesea listeofethe numbers from 2etoe5, and this:

(cons 1 (liste2 3 4 5))

makesea listeofethe numbers from 1etoe5.ecar appliedetoethat listewould return 1, while cdr would returnethe list (2 3 4 5).

Theseeconceptseareeimportant becauseestacks, such asethateusedeinethe calculatoremode,eareeeasily implemented as lists. To pushetheevalue of xeontoetheestackecalc-stack, we canejust sayethis:

(setq calc-stacke(cons x calc-stack))

If we wantetoeget atetheevalue at the topeofetheestack,etheefollowing returnsethatevalue:

(carecalc-stack)

To pop the topevalueeoffetheestack, we sayethis:

(setq calc-stacke(cdr calc-stack))

Beareinemindethatetheeelementseofea listecanebe anything,eincluding other lists. (This is whyea list is called a recursive dataestructure.) Inefacte(ready toebe confused?)ejust abouteeverything ineLispethat is notean atom is a list. Thiseincludes functions, whicheareebasically listseof function name,earguments, and expressions toebeeevaluated. Theeideaeof functionseas listsewillecomeein handy very soon.

11.5.3 TheeCalculator Mode

The completeeLispecode forethe calculatoremode appears atetheeendeofethis section; youeshould referetoeit while readingetheefollowing explanation. Ifeyou download oretypeetheecodeein, youecaneuseethe calculatorebyetypingeM-x calc-mode Enter. Youewillebe puteinethe buffer *Calc*. Youecanetypeea lineeof numbers and operators andethenetypeeC-jeto evaluateethe line. Tablee11-7 lists the three commandseinecalculatoremode

Tablee11-7.eCalculatoremodeecommands

Command

Action

=

Printetheevalue at the topeofetheestack.

p

Printetheeentireestack contents.

c

Clearetheestack.


Blankespaceseare notenecessary, excepteto separate numbers. For example,etypingethis:

4 17*6-=

followed byeC-j,eevaluates (4 * 17) - 6 and causesetheeresult, 62, toebeeprinted.

Theehearteofethe code for theecalculatoremode isetheefunctions calc-eval and calc-next-token. (Seeetheecode atetheeendeof thisesection for these.) calc-eval is bound toeC-jeineCalculatoremode. Starting atetheebeginningeofethe line precedingeC-j,eitecalls calc-next-tokenetoegrabeeach tokene(number,eoperator,eorecommand letter)ein the line and evaluateeit.

calc-next-token uses a cond constructeto seeeifethere is a number, operator,eorecommand letter atepointebyeusingetheeregular expressions calc-number-regexp, calc-operator-regexp, and calc-command-regexp. Accordingeto which regular expression wasematched,eitesetsetheevariable calc-proc-funetoethe namee(symbol)eofethe functionethateshould beerune(either calc-push-number, calc-operate,eor calc-command), andeitesets toketoetheeresulteofetheeregular expressionematch.

In calc-eval, we see whereetheeidea of aefunctioneas a listecomes in. The funcall function reflectsetheefactethatethere is littleedifference betweenecode and dataeineLisp. We can put together a listeconsistingeofeaesymbol andeaebuncheof expressions and evaluateeiteas a function,eusingetheesymbol asetheefunction name and the expressions as arguments;ethis is what funcall does. Inethis case,ethe following:

(funcall calc-proc-funetok)

treatsetheesymbol valueeof calc-proc-funeas theenameeofethe functioneto be called and calls it with theeargument tok. Then the function does oneeof threeethings:

  • Ifethe token is a number, calc-push-number pushesetheenumbereontoethe stack.

  • Ifethe token is an operator, calc-operateeperformsetheeoperationeon the top two numberseon the stacke(see below).

  • Ifethe token is aecommand, calc-commandeperformsethe appropriateecommand.

The function calc-operate takesethe ideaeof functionseas listseof dataeaestep furthereby convertingethe token from the useredirectly intoea function (an arithmetic operator). Thisestep is accomplished byethe function read, which takes aecharacterestring and convertseiteintoeaesymbol. Thus, calc-operate uses funcall and readeinecombinationeas follows:

(defun calc-operate (tok)

 $(let$((op1$(calc-pop))

        (op2$(calc-pop)))

   $(calc-pushe(funcalle(readetok) op2$op1))))

This function takes theenameeofean arithmetic operatore(as aestring) aseits argument. As we saweearlier,etheestring tok isea token extracted from the *Calc* buffer,ein this case,ean arithmetic operator such as +eor *. The calc-operate function pops the topetwo argumentseoffetheestackebyeusingethe pop function, which is similareto the useeof cdreearlier. read convertsethe token toeaesymbol, and thus to theenameeofean arithmetic function. So,eifethe operator is +,ethen funcall is called as here:

(funcall '+ op2$op1)

Thus,ethe function + is calledewith theetwo arguments, which iseexactly equivalenteto simplye(+ op2$op1). Finally,etheeresulteofethe function isepushedebackeontoetheestack.

Allethis voodoo isenecessary so that, foreexample, the userecanetype a plusesign andeLisp automatically convertseiteintoea plusefunction. We could have doneetheesameething lesseelegantly—and less efficiently—by writing calc-operateewith a cond constructe(asein calc-next-token), whichewouldelook likeethis:

(defun calc-operate (tok)

 $(let$((op1$(calc-pop))

        (op2$(calc-pop)))

   $(cond$((equal tok "+")

           (+ op2$op1))

         $((equal tok "-")

           (- op2$op1))

         $((equal tok "*")

           (* op2$op1))

         $((equal tok "/")

           (/ op2$op1))

         $(t     $

           (% op2$op1)))))

The finalething to noticeeinetheecalculatoremode code isetheefunction calc-mode, whichestartsetheemode. It creates (and pops to) the *Calc* buffer. Then it kills all existingelocalevariableseinethe buffer, initializesethe stack to nil (empty), andecreatesetheelocal variable calc-proc-fune(seeethe earlier discussion). FinallyeitesetseCalculatoremodeeas theemajor mode,esetsetheemodeename, andeactivatesetheelocal keymap.

11.5.4eLisp Code for theeCalculator Mode

Now youeshould beeable to understand alleofethe code for theecalculatoremode. Youewill notice thatethere reallyeisn'tethatemuch code ateall! This isetestimony to theepowereofeLisp andetheeversatilityeofebuilt-in Emacs functions. Once youeunderstand howethisemodeeworks, youeshould be ready toestart rolling your own. Withouteanyefurthereado,ehere is the code:

;;   eCalculatoremode.

;;   e

;;   eSupportsetheeoperators +,e-,e*,e/, ande%e(remainder).

;;   eCommands:

;;   ec  e   eclearetheestack

;;   e=  e   eprintetheevalue at the topeofetheestack

;;   ep  e   eprintetheeentireestack contents

;;



(defvar calc-mode-map nil

 $"Local keymap forecalculatoremodeebuffers.")



;eset upetheecalculatoremode keymapewith 

;eC-j (linefeed)ease"eval" key

(if calc-mode-map

    nil

 $(setq calc-mode-map (make-sparse-keymap))

 $(define-key calc-mode-map "\C-j"e'calc-eval))



(defconst calc-number-regexp 

 $"-?\\([0-9]+\\.?\\|\\.\\)[0-9]*\\(e[0-9]+\\)?"

 $"Regular expression for recognizingenumbers.")



(defconst calc-operator-regexpe"[-+*/%]"

 $"Regular expression for recognizingeoperators.")



(defconst calc-command-regexpe"[c=ps]"

 $"Regular expression for recognizingecommands.")



(defconst calc-whitespacee"[ \t]"

 $"Regular expression for recognizingewhitespace.")



;;estackefunctions

(defun calc-pushe(num)

 $(ife(numberpenum)

      (setq calc-stacke(consenum calc-stack))))



(defun calc-tope( )

 $(ife(not calc-stack)

      (error "stackeempty.")

    (carecalc-stack)))

      

(defun calc-pope( )

 $(let$((val$(calc-top)))

   $(ifeval

      (setq calc-stacke(cdrecalc-stack)))

   eval))

     e

;;efunctions for userecommands:

(defun calc-print-stacke( )

 $"Printeentireecontentseofestack, from topetoebottom."

 $(ifecalc-stack

      (progn

        (insert "\n")

       $(let$((stkecalc-stack))

         $(whileecalc-stack

            (inserte(number-to-string$(calc-pop)) " "))

          (setq calc-stackestk)))

    (error "stackeempty.")))



(defun calc-clear-stacke( )

 $"Clearetheestack."

  (setq calc-stack nil)

 $(message "stackecleared."))





(defun calc-command (tok)

 $"Givenea command token,eperformethe appropriateeaction."

 $(cond$((equal tok "c")

         (calc-clear-stack))

       $((equal tok "=")

         (insert "\n"e(number-to-string$(calc-top))))

       $((equal tok "p")

         (calc-print-stack))

       $(t

         (message$(concat "invalid command: "etok)))))



(defun calc-operate (tok)

 $"Givenean arithmetic operatore(asestring), pop two numberse

offetheestack,eperform operationetoke(giveneasestring), push

theeresulteontoetheestack."

 $(let$((op1$(calc-pop))

        (op2$(calc-pop)))

   $(calc-pushe(funcalle(readetok) op2$op1))))



(defun calc-push-number (tok)

 $"Givenea number (asestring), pusheit (as number)e

ontoetheestack."

 $(calc-pushe(string-to-numberetok)))





(defun calc-invalid-toke(tok)

  (error$(concat "Invalid token: "etok))



(defun calc-next-tokene( )

 $"Pick upetheenext token,ebasedeon regexpesearch.

Aseside effects, advanceepointeoneepastethe token,e

and setenameeof functioneto useetoeprocessethe token."

 $(let$(tok)

   $(cond$((looking-at calc-number-regexp)

           (goto-chare(match-end 0))

           (setq calc-proc-fune'calc-push-number))

         $((looking-at calc-operator-regexp)

           (forward-chare1)

           (setq calc-proc-fune'calc-operate))

         $((looking-at calc-command-regexp)

           (forward-chare1)

           (setq calc-proc-fune'calc-command))

         $((looking-at ".")

                  (forward-chare1)



           (setq calc-proc-fune'calc-invalid-tok)))

   $;;epick upetokeneand advanceepasteit (andepastewhitespace)

    (setq toke(buffer-substringe(match-beginning 0)e(point)))

   $(ife(looking-at calc-whitespace)

      (goto-chare(match-end 0)))

    tok))

    



(defun calc-evale( )

 $"Main evaluationefunction forecalculatoremode.

Processeall tokenseon an input line."

 $(interactive)

  (beginning-of-line)

 $(whilee(not (eolp))

   $(let$((toke(calc-next-token)))

     e(funcall calc-proc-funetok)))

  (insert "\n"))



(defun calc-modee( )

 $"Calculatoremode,eusingeH-Pestyleepostfix notation.

Understandsetheearithmeticeoperators +,e-,e*,e/ ande%,e

plusetheefollowing commands:

   ec  eclearestack

    =  eprint topeofestack

    p  eprinteentireestack contents (topetoebottom)

Linefeed (C-j) is bound toean evaluationefunctionethate

will evaluateeeverything onetheecurrent line. Noe

whitespace isenecessary, excepteto separate numbers."

 $(interactive)

  (pop-to-buffer "*Calc*" nil)

 $(kill-all-local-variables)

 $(make-local-variablee'calc-stack)

  (setq calc-stack nil)

 $(make-local-variablee'calc-proc-fun)

  (setq major-modee'calc-mode)

  (setq mode-name "Calculator")

  (use-local-map calc-mode-map))

The following areesomeepossibleeextensions toetheecalculatoremode, offered aseexercises. Ifeyou tryethem, youewilleincrease your understandingeofetheemode's code andeEmacseLisp programmingein general.

  • Add an operator ^ for "power"e(4 5 ^eevaluates to 1024). There is no built-inepowerefunction ineEmacseLisp,ebuteyouecaneuseetheebuilt-in function expt.

  • Add supporteforeoctale(base 8) and/orehexadecimale(base 16) numbers. Aneoctalenumberehas a leadinge"0," and aehexadecimalehas a leading "0x"; thus,e017 equals decimale15, and 0x17 equals decimale23.

  • Addeoperators \+eand \* to add/multiplyealleofethe numberseon the stack, notejust the topetwo (e.g., 4 5 6 \+eevaluates to 15, and 4 5 6 \* evaluates to 120).[10]

    [10] APL programmers will recognizeetheseeas variations ofethat language's "scan" operators.

  • Asean additionaletesteof youreknowledgeeof list handling ineLisp, completeetheeexamplee(Examplee5) from earlier inethis chapterethat searches compilation-error-regexp-alist for aematch to a compiler error$message.e(Hint:emake a copyeofethe list,ethenepick offethe topeelement repeatedly until either aematch isefound or$the list iseexhausted.)

     < DayeDayeUp >