From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2 ([2001:41d0:8:6d80::]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id IAIYDLdMfmDQ9AAAgWs5BA (envelope-from ) for ; Tue, 20 Apr 2021 05:38:31 +0200 Received: from aspmx1.migadu.com ([2001:41d0:8:6d80::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2 with LMTPS id uJrTBrdMfmCPZwAAB5/wlQ (envelope-from ) for ; Tue, 20 Apr 2021 03:38:31 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id B7F3A18EC9 for ; Tue, 20 Apr 2021 05:38:29 +0200 (CEST) Received: from localhost ([::1]:37230 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lYhDX-0008Qp-Hu for larch@yhetil.org; Mon, 19 Apr 2021 23:38:27 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:52622) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lYhD9-0008Qa-LM for emacs-orgmode@gnu.org; Mon, 19 Apr 2021 23:38:03 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:21803) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lYhD6-0004Pd-DL for emacs-orgmode@gnu.org; Mon, 19 Apr 2021 23:38:02 -0400 Received: from pmg2.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id A172B8078F; Mon, 19 Apr 2021 23:37:57 -0400 (EDT) Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id D007B805D6; Mon, 19 Apr 2021 23:37:50 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1618889870; bh=sm7JJ1T1U9DcHdIXACYTmAYWlQOfbLQ63GUGsMgx7ig=; h=From:To:Cc:Subject:Date:From; b=R4ymJcDZQiE3g9cLbOr/GgaxH3+iLOyEZ9fOsF+av41h/FEX2/fLBC5kvrSlkddst nk4Rzi0SLJiITZZ8p3eOeaa8cZ9SPCwHS8eEy0yVCHCP2xVnaYdJB+55MUi/DgeRJh wxuhJR2X2ZKZD2f5J7ZObXlTuHHw/apC8BgL1AEOzD+oaUxSkz8CS4xK6HKeQyblaX /FLVvsYCNMVnvQiPv5iX5MaNXQQ3304TPBFf0BtK+7xHu9nPSvM/gdKO902hF2GAOf 42aqdYWefWty292t6q88boGDDULQqujgj81yLduukzVWzsRfupz9K9CHLTHWZVhvSW UQ9Hr+FGNEJ4g== Received: from alfajor (104-222-126-84.cpe.teksavvy.com [104.222.126.84]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 9FA1E120341; Mon, 19 Apr 2021 23:37:50 -0400 (EDT) From: Stefan Monnier To: Nicolas Goaziou Subject: More use of lexical-binding in ox.el Message-ID: Date: Mon, 19 Apr 2021 23:37:50 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.059 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain Received-SPF: pass client-ip=132.204.25.50; envelope-from=monnier@iro.umontreal.ca; helo=mailscanner.iro.umontreal.ca X-Spam_score_int: -42 X-Spam_score: -4.3 X-Spam_bar: ---- X-Spam_report: (-4.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: emacs-orgmode@gnu.org Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: "Emacs-orgmode" X-Migadu-Flow: FLOW_IN ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1618889910; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=man1wqq3CEnM+iMMjfKAvhbJwHrPpeVlLzZlwgR5jAk=; b=QU0rqod4V0Zd4zR9nUOjn/XHkg8F4yiRyNGYdzOvW2LfxGBViJnNA1Jqt0msBfW03tnKHA mbLNifO67FHftQNhd0mrkP6bBFkGrM1DjcP8qqM9RJdEFXcuztgi29FSR9ynbW7LDvQO4L szFj1F9J4w1UieEBXjhJAgJYbP8g5cA88f7LsNoJ3BnQWS94u0Xl5oNjnxji8TswmEaAch PsTPCoARYXvQgeLCFS3v6/IubWP/vPiXCkcbBPF02U927OH+yLzCnI0RMJiYb5F1rI5LJw OKYZ+slyB1x90kAgXgr2EPIYszCywHDypJGZWdcnaYyvUdJz8ElWCsEogKi1BQ== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1618889910; a=rsa-sha256; cv=none; b=kfRSkhOdEA6thPVz0nvgnNKYOm+Q4MgCkeRjExzBbM+Lxsbcb0f1V2tvKr85Th4rUqBMpC PRsotbkwArVQKKD9M20zpbnn/wPlhoXyoORIM6b8/kLC8OODeuPRUuJUg3cLIbrvKLYsFl pwlz2e1ztWyUaxXpCFJKoORCgzp2ac/LprUUgLGMSvJgyKGCcsy7MzI4u4Flya04que+p4 0QQOT+yGSKofYIVi9UcQ5POPAq3+pEMdzSaZ9tDuMYkZHR/ZDo5DAdZ4WB/nQosSulF6lx J9/00NovQ+zwlnhkx0fDxBneUrAwROwNNGuUNtVqzhd5QdcXVMOf26uGDVmDug== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=pass header.d=iro.umontreal.ca header.s=mail header.b=R4ymJcDZ; spf=pass (aspmx1.migadu.com: domain of emacs-orgmode-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=emacs-orgmode-bounces@gnu.org X-Migadu-Spam-Score: -3.14 Authentication-Results: aspmx1.migadu.com; dkim=pass header.d=iro.umontreal.ca header.s=mail header.b=R4ymJcDZ; dmarc=pass (policy=none) header.from=iro.umontreal.ca; spf=pass (aspmx1.migadu.com: domain of emacs-orgmode-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=emacs-orgmode-bounces@gnu.org X-Migadu-Queue-Id: B7F3A18EC9 X-Spam-Score: -3.14 X-Migadu-Scanner: scn0.migadu.com X-TUID: fzLqTiRx3mlR Here's another patch to remove some more use of the old dynamically scoped dialect of ELisp. Stefan * lisp/ox.el: Fix various uses of the non-lexical-binding ELisp dialect. (org-export--get-global-options, org-export-insert-default-template): Use lexical-binding. (org-export--generate-copy-script): Return a closure rather than list starting with `lambda`. (org-export-async-start): Turn it into a function (there seems to be no reason this was a macro). Use `write-region` rather than `with-temp-file`. Always use `utf-8-emacs-unix` coding system since it's more efficient and is guaranteed to handle all chars. Use lexical-binding in the temp file as well. Actually set `debug-on-error` if `org-export-async-debug` says so. (org-export-to-buffer, org-export-to-file): Pass a closure rather than list starting with `lambda` to `org-export-async-start`. diff --git a/lisp/ox.el b/lisp/ox.el index 758b9370b3..2ce8985a9e 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -1571,7 +1571,7 @@ process." plist prop ;; Evaluate default value provided. - (let ((value (eval (nth 3 cell)))) + (let ((value (eval (nth 3 cell) t))) (if (eq (nth 4 cell) 'parse) (org-element-parse-secondary-string value (org-element-restriction 'keyword)) @@ -2561,51 +2561,59 @@ another buffer, effectively cloning the original buffer there. The function assumes BUFFER's major mode is `org-mode'." (with-current-buffer buffer - `(lambda () - (let ((inhibit-modification-hooks t)) - ;; Set major mode. Ignore `org-mode-hook' as it has been run - ;; already in BUFFER. - (let ((org-mode-hook nil) (org-inhibit-startup t)) (org-mode)) - ;; Copy specific buffer local variables and variables set - ;; through BIND keywords. - ,@(let ((bound-variables (org-export--list-bound-variables)) - vars) - (dolist (entry (buffer-local-variables (buffer-base-buffer)) vars) + (let ((str (org-with-wide-buffer (buffer-string))) + (narrowing + (if (org-region-active-p) + (list (region-beginning) (region-end)) + (list (point-min) (point-max)))) + (pos (point)) + (varvals + (let ((bound-variables (org-export--list-bound-variables)) + varvals) + (dolist (entry (buffer-local-variables (buffer-base-buffer))) (when (consp entry) (let ((var (car entry)) (val (cdr entry))) (and (not (memq var org-export-ignored-local-variables)) (or (memq var '(default-directory - buffer-file-name - buffer-file-coding-system)) + buffer-file-name + buffer-file-coding-system)) (assq var bound-variables) (string-match "^\\(org-\\|orgtbl-\\)" (symbol-name var))) ;; Skip unreadable values, as they cannot be ;; sent to external process. (or (not val) (ignore-errors (read (format "%S" val)))) - (push `(set (make-local-variable (quote ,var)) - (quote ,val)) - vars)))))) - ;; Whole buffer contents. - (insert ,(org-with-wide-buffer (buffer-string))) - ;; Narrowing. - ,(if (org-region-active-p) - `(narrow-to-region ,(region-beginning) ,(region-end)) - `(narrow-to-region ,(point-min) ,(point-max))) - ;; Current position of point. - (goto-char ,(point)) - ;; Overlays with invisible property. - ,@(let (ov-set) - (dolist (ov (overlays-in (point-min) (point-max)) ov-set) + (push (cons var val) varvals)))) + varvals))) + (ols + (let (ov-set) + (dolist (ov (overlays-in (point-min) (point-max))) (let ((invis-prop (overlay-get ov 'invisible))) (when invis-prop - (push `(overlay-put - (make-overlay ,(overlay-start ov) - ,(overlay-end ov)) - 'invisible (quote ,invis-prop)) - ov-set))))))))) + (push (list (overlay-start ov) (overlay-end ov) + invis-prop) + ov-set)))) + ov-set))) + (lambda () + (let ((inhibit-modification-hooks t)) + ;; Set major mode. Ignore `org-mode-hook' as it has been run + ;; already in BUFFER. + (let ((org-mode-hook nil) (org-inhibit-startup t)) (org-mode)) + ;; Copy specific buffer local variables and variables set + ;; through BIND keywords. + (pcase-dolist (`(,var . ,val) varvals) + (set (make-local-variable var) val)) + ;; Whole buffer contents. + (insert str) + ;; Narrowing. + (apply #'narrow-to-region narrowing) + ;; Current position of point. + (goto-char pos) + ;; Overlays with invisible property. + (pcase-dolist (`(,start ,end ,invis) ols) + (overlay-put (make-overlay start end) 'invisible invis))))))) (defun org-export--delete-comment-trees () "Delete commented trees and commented inlinetasks in the buffer. @@ -3104,11 +3112,11 @@ locally for the subtree through node properties." (keyword (unless (assoc keyword keywords) (let ((value (if (eq (nth 4 entry) 'split) - (mapconcat #'identity (eval (nth 3 entry)) " ") - (eval (nth 3 entry))))) + (mapconcat #'identity (eval (nth 3 entry) t) " ") + (eval (nth 3 entry) t)))) (push (cons keyword value) keywords)))) (option (unless (assoc option options) - (push (cons option (eval (nth 3 entry))) options)))))) + (push (cons option (eval (nth 3 entry) t)) options)))))) ;; Move to an appropriate location in order to insert options. (unless subtreep (beginning-of-line)) ;; First (multiple) OPTIONS lines. Never go past fill-column. @@ -3119,7 +3127,7 @@ locally for the subtree through node properties." (sort options (lambda (k1 k2) (string< (car k1) (car k2))))))) (if subtreep (org-entry-put - node "EXPORT_OPTIONS" (mapconcat 'identity items " ")) + node "EXPORT_OPTIONS" (mapconcat #'identity items " ")) (while items (insert "#+options:") (let ((width 10)) @@ -3609,7 +3617,7 @@ will become the empty string." (attributes (let ((value (org-element-property attribute element))) (when value - (let ((s (mapconcat 'identity value " ")) result) + (let ((s (mapconcat #'identity value " ")) result) (while (string-match "\\(?:^\\|[ \t]+\\)\\(:[-a-zA-Z0-9_]+\\)\\([ \t]+\\|$\\)" s) @@ -4702,7 +4710,7 @@ code." ;; should start six columns after the widest line of code, ;; wrapped with parenthesis. (max-width - (+ (apply 'max (mapcar 'length code-lines)) + (+ (apply #'max (mapcar #'length code-lines)) (if (not num-start) 0 (length (format num-fmt num-start)))))) (org-export-format-code code @@ -6200,91 +6208,87 @@ to `:default' encoding. If it fails, return S." ;; For back-ends, `org-export-add-to-stack' add a new source to stack. ;; It should be used whenever `org-export-async-start' is called. -(defmacro org-export-async-start (fun &rest body) +(defun org-export-async-start (fun body) "Call function FUN on the results returned by BODY evaluation. -FUN is an anonymous function of one argument. BODY evaluation -happens in an asynchronous process, from a buffer which is an -exact copy of the current one. +FUN is an anonymous function of one argument. BODY should be a valid +ELisp source expression. BODY evaluation happens in an asynchronous process, +from a buffer which is an exact copy of the current one. Use `org-export-add-to-stack' in FUN in order to register results in the stack. This is a low level function. See also `org-export-to-buffer' and `org-export-to-file' for more specialized functions." - (declare (indent 1) (debug t)) - (org-with-gensyms (process temp-file copy-fun proc-buffer coding) - ;; Write the full sexp evaluating BODY in a copy of the current - ;; buffer to a temporary file, as it may be too long for program - ;; args in `start-process'. - `(with-temp-message "Initializing asynchronous export process" - (let ((,copy-fun (org-export--generate-copy-script (current-buffer))) - (,temp-file (make-temp-file "org-export-process")) - (,coding buffer-file-coding-system)) - (with-temp-file ,temp-file - (insert - ;; Null characters (from variable values) are inserted - ;; within the file. As a consequence, coding system for - ;; buffer contents will not be recognized properly. So, - ;; we make sure it is the same as the one used to display - ;; the original buffer. - (format ";; -*- coding: %s; -*-\n%S" - ,coding - `(with-temp-buffer - (when org-export-async-debug '(setq debug-on-error t)) - ;; Ignore `kill-emacs-hook' and code evaluation - ;; queries from Babel as we need a truly - ;; non-interactive process. - (setq kill-emacs-hook nil - org-babel-confirm-evaluate-answer-no t) - ;; Initialize export framework. - (require 'ox) - ;; Re-create current buffer there. - (funcall ,,copy-fun) - (restore-buffer-modified-p nil) - ;; Sexp to evaluate in the buffer. - (print (progn ,,@body)))))) - ;; Start external process. - (let* ((process-connection-type nil) - (,proc-buffer (generate-new-buffer-name "*Org Export Process*")) - (,process - (apply - #'start-process - (append - (list "org-export-process" - ,proc-buffer - (expand-file-name invocation-name invocation-directory) - "--batch") - (if org-export-async-init-file - (list "-Q" "-l" org-export-async-init-file) - (list "-l" user-init-file)) - (list "-l" ,temp-file))))) - ;; Register running process in stack. - (org-export-add-to-stack (get-buffer ,proc-buffer) nil ,process) - ;; Set-up sentinel in order to catch results. - (let ((handler ,fun)) - (set-process-sentinel - ,process - `(lambda (p status) - (let ((proc-buffer (process-buffer p))) - (when (eq (process-status p) 'exit) - (unwind-protect - (if (zerop (process-exit-status p)) - (unwind-protect - (let ((results - (with-current-buffer proc-buffer - (goto-char (point-max)) - (backward-sexp) - (read (current-buffer))))) - (funcall ,handler results)) - (unless org-export-async-debug - (and (get-buffer proc-buffer) - (kill-buffer proc-buffer)))) - (org-export-add-to-stack proc-buffer nil p) - (ding) - (message "Process `%s' exited abnormally" p)) - (unless org-export-async-debug - (delete-file ,,temp-file))))))))))))) + (declare (indent 1)) + ;; Write the full sexp evaluating BODY in a copy of the current + ;; buffer to a temporary file, as it may be too long for program + ;; args in `start-process'. + (with-temp-message "Initializing asynchronous export process" + (let ((copy-fun (org-export--generate-copy-script (current-buffer))) + (temp-file (make-temp-file "org-export-process"))) + (let ((coding-system-for-write 'utf-8-emacs-unix)) + (write-region + ;; Null characters (from variable values) are inserted + ;; within the file. As a consequence, coding system for + ;; buffer contents could fail to be recognized properly. + (format ";; -*- coding: utf-8-emacs-unix; lexical-binding:t -*-\n%S" + `(with-temp-buffer + ,(when org-export-async-debug '(setq debug-on-error t)) + ;; Ignore `kill-emacs-hook' and code evaluation + ;; queries from Babel as we need a truly + ;; non-interactive process. + (setq kill-emacs-hook nil + org-babel-confirm-evaluate-answer-no t) + ;; Initialize export framework. + (require 'ox) + ;; Re-create current buffer there. + (funcall ',copy-fun) + (restore-buffer-modified-p nil) + ;; Sexp to evaluate in the buffer. + (print ,body))) + nil temp-file nil 'silent)) + ;; Start external process. + (let* ((process-connection-type nil) + (proc-buffer (generate-new-buffer-name "*Org Export Process*")) + (process + (apply + #'start-process + (append + (list "org-export-process" + proc-buffer + (expand-file-name invocation-name invocation-directory) + "--batch") + (if org-export-async-init-file + (list "-Q" "-l" org-export-async-init-file) + (list "-l" user-init-file)) + (list "-l" temp-file))))) + ;; Register running process in stack. + (org-export-add-to-stack (get-buffer proc-buffer) nil process) + ;; Set-up sentinel in order to catch results. + (let ((handler fun)) + (set-process-sentinel + process + (lambda (p _status) + (let ((proc-buffer (process-buffer p))) + (when (eq (process-status p) 'exit) + (unwind-protect + (if (zerop (process-exit-status p)) + (unwind-protect + (let ((results + (with-current-buffer proc-buffer + (goto-char (point-max)) + (backward-sexp) + (read (current-buffer))))) + (funcall handler results)) + (unless org-export-async-debug + (and (get-buffer proc-buffer) + (kill-buffer proc-buffer)))) + (org-export-add-to-stack proc-buffer nil p) + (ding) + (message "Process `%s' exited abnormally" p)) + (unless org-export-async-debug + (delete-file temp-file)))))))))))) ;;;###autoload (defun org-export-to-buffer @@ -6325,14 +6329,15 @@ This function returns BUFFER." (declare (indent 2)) (if async (org-export-async-start - `(lambda (output) - (with-current-buffer (get-buffer-create ,buffer) - (erase-buffer) - (setq buffer-file-coding-system ',buffer-file-coding-system) - (insert output) - (goto-char (point-min)) - (org-export-add-to-stack (current-buffer) ',backend) - (ignore-errors (funcall ,post-process)))) + (let ((cs buffer-file-coding-system)) + (lambda (output) + (with-current-buffer (get-buffer-create buffer) + (erase-buffer) + (setq buffer-file-coding-system cs) + (insert output) + (goto-char (point-min)) + (org-export-add-to-stack (current-buffer) backend) + (ignore-errors (funcall post-process))))) `(org-export-as ',backend ,subtreep ,visible-only ,body-only ',ext-plist)) (let ((output @@ -6391,8 +6396,8 @@ or FILE." (encoding (or org-export-coding-system buffer-file-coding-system))) (if async (org-export-async-start - `(lambda (file) - (org-export-add-to-stack (expand-file-name file) ',backend)) + (lambda (file) + (org-export-add-to-stack (expand-file-name file) backend)) `(let ((output (org-export-as ',backend ,subtreep ,visible-only ,body-only @@ -6523,16 +6528,16 @@ within Emacs." (defvar org-export-stack-mode-map (let ((km (make-sparse-keymap))) (set-keymap-parent km tabulated-list-mode-map) - (define-key km " " 'next-line) - (define-key km "\C-n" 'next-line) - (define-key km [down] 'next-line) - (define-key km "\C-p" 'previous-line) - (define-key km "\C-?" 'previous-line) - (define-key km [up] 'previous-line) - (define-key km "C" 'org-export-stack-clear) - (define-key km "v" 'org-export-stack-view) - (define-key km (kbd "RET") 'org-export-stack-view) - (define-key km "d" 'org-export-stack-remove) + (define-key km " " #'next-line) + (define-key km "\C-n" #'next-line) + (define-key km [down] #'next-line) + (define-key km "\C-p" #'previous-line) + (define-key km "\C-?" #'previous-line) + (define-key km [up] #'previous-line) + (define-key km "C" #'org-export-stack-clear) + (define-key km "v" #'org-export-stack-view) + (define-key km (kbd "RET") #'org-export-stack-view) + (define-key km "d" #'org-export-stack-remove) km) "Keymap for Org Export Stack.") @@ -6749,16 +6754,16 @@ back to standard interface." (cond ((and (numberp key-a) (numberp key-b)) (< key-a key-b)) ((numberp key-b) t))))) - 'car-less-than-car)) + #'car-less-than-car)) ;; Compute a list of allowed keys based on the first key ;; pressed, if any. Some keys ;; (?^B, ?^V, ?^S, ?^F, ?^A, ?&, ?# and ?q) are always ;; available. (allowed-keys (nconc (list 2 22 19 6 1) - (if (not first-key) (org-uniquify (mapcar 'car entries)) + (if (not first-key) (org-uniquify (mapcar #'car entries)) (let (sub-menu) - (dolist (entry entries (sort (mapcar 'car sub-menu) '<)) + (dolist (entry entries (sort (mapcar #'car sub-menu) #'<)) (when (eq (car entry) first-key) (setq sub-menu (append (nth 2 entry) sub-menu)))))) (cond ((eq first-key ?P) (list ?f ?p ?x ?a))