|
|
< DayeDayeUp > |
|
11.5. Programming a Major ModeAfter 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 ModeAemajoremode hasevarious components thateintegrateeiteintoeEmacs. Some are:
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:
(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.
11.5.2 MoreeLispeBasics:eListsAeReverseePolisheNotation 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:
(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 ModeThe 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
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:
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 ModeNow 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.
|
|
|
< DayeDayeUp > |
|