branch: master commit 68d6973ce1509a00d26f86a3addff28b7c42222e Author: Ian Dunn <du...@gnu.org> Commit: Ian Dunn <du...@gnu.org>
Fixed parsing multiple forms inside if/then/else blocks * org-edna.el (org-edna--normalize-forms): New defun. (org-edna--normalize-sexp-form): Use it for if-statements (org-edna--normalize-all-forms): Use it. * org-edna-tests.el: Added new parsing tests. --- org-edna-tests.el | 86 ++++++++++++++++++++++++++++++++++++++++++++++++------- org-edna.el | 61 +++++++++++++++++++++++++-------------- org-edna.info | 13 ++++++--- org-edna.org | 5 ++++ 4 files changed, 130 insertions(+), 35 deletions(-) diff --git a/org-edna-tests.el b/org-edna-tests.el index 01842ec..0a9ddca 100644 --- a/org-edna-tests.el +++ b/org-edna-tests.el @@ -175,10 +175,10 @@ (sexp (org-edna-string-form-to-sexp-form input-string 'action))) (should (equal sexp - '(((if ((match "checklist") - (done?)) - ((self) - (todo! TODO)) + '(((if (((match "checklist") + (done?))) + (((self) + (todo! TODO))) nil))))))) (ert-deftest org-edna-form-to-sexp-if-else () @@ -186,12 +186,78 @@ (sexp (org-edna-string-form-to-sexp-form input-string 'action))) (should (equal sexp - '(((if ((match "checklist") - (done?)) - ((self) - (todo! TODO)) - ((siblings) - (todo! DONE))))))))) + '(((if (((match "checklist") + (done?))) + (((self) + (todo! TODO))) + (((siblings) + (todo! DONE)))))))))) + +(ert-deftest org-edna-form-to-sexp-if-multiple-thens () + (let* ((input-string "if match(\"checklist\") done? then self next-sibling todo!(TODO) self set-property!(\"COUNTER\" \"0\") endif") + (sexp (org-edna-string-form-to-sexp-form input-string 'action))) + (should (equal + sexp + '(((if (((match "checklist") + (done?))) + (((self) + (next-sibling) + (todo! TODO)) + ((self) + (set-property! "COUNTER" "0"))) + nil))))))) + +(ert-deftest org-edna-form-to-sexp-if-multiple-elses () + (let* ((input-string "if match(\"checklist\") done? then self todo!(TODO) else siblings todo!(DONE) self todo!(TODO) endif") + (sexp (org-edna-string-form-to-sexp-form input-string 'action))) + (should (equal + sexp + '(((if (((match "checklist") + (done?))) + (((self) + (todo! TODO))) + (((siblings) + (todo! DONE)) + ((self) + (todo! TODO)))))))))) + +(ert-deftest org-edna-form-to-sexp-failed-if () + (pcase-let* ((input-string "if match(\"checklist\") done?") + (`(,error . ,data) (should-error (org-edna-string-form-to-sexp-form + input-string 'action) + :type 'invalid-read-syntax))) + (should (eq error 'invalid-read-syntax)) + (should (listp data)) + (should (eq (length data) 6)) + (should (string-equal (plist-get data :msg) "Malformed if-construct; expected then terminator")) + ;; Error should point to the start of the if-statement + (should (eq (plist-get data :error-pos) 0)))) + +(ert-deftest org-edna-form-to-sexp-failed-if-then () + (pcase-let* ((input-string "if match(\"checklist\") done? then") + (`(,error . ,data) (should-error (org-edna-string-form-to-sexp-form + input-string 'action) + :type 'invalid-read-syntax))) + (should (eq error 'invalid-read-syntax)) + (should (listp data)) + (should (eq (length data) 6)) + (should (string-equal (plist-get data :msg) + "Malformed if-construct; expected else or endif terminator")) + ;; Error should point to the start of the if-statement + (should (eq (plist-get data :error-pos) 28)))) + +(ert-deftest org-edna-form-to-sexp-failed-if-then-else () + (pcase-let* ((input-string "if match(\"checklist\") done? then todo!(TODO) else todo!(TODO)") + (`(,error . ,data) (should-error (org-edna-string-form-to-sexp-form + input-string 'action) + :type 'invalid-read-syntax))) + (should (eq error 'invalid-read-syntax)) + (should (listp data)) + (should (eq (length data) 6)) + (should (string-equal (plist-get data :msg) + "Malformed if-construct; expected endif terminator")) + ;; Error should point to the start of the if-statement + (should (eq (plist-get data :error-pos) 45)))) (ert-deftest org-edna-expand-sexp-form () ;; Override cl-gentemp so we have a repeatable test diff --git a/org-edna.el b/org-edna.el index a72377b..a4d9d3e 100644 --- a/org-edna.el +++ b/org-edna.el @@ -218,44 +218,50 @@ the remainder of FORM after the current scope was parsed." ;; ending. If it doesn't match, throw an error. (let (cond-form then-form else-form have-else) (pcase-let* ((`(,temp-form ,r-form) - (org-edna--normalize-sexp-form + (org-edna--normalize-forms remaining-form ;; Only allow conditions in cond forms 'condition + '((then)) from-string))) - ;; Use car-safe to catch r-form = nil - (unless (equal (car-safe r-form) '(then)) + (unless r-form (org-edna--syntax-error "Malformed if-construct; expected then terminator" - from-string (cdr (car-safe r-form)))) + from-string error-pos)) + ;; Skip the 'then' construct and move forward (setq cond-form temp-form + error-pos (cdar r-form) remaining-form (cdr r-form))) + (pcase-let* ((`(,temp-form ,r-form) - (org-edna--normalize-sexp-form remaining-form - action-or-condition - from-string))) - (unless (member (car-safe r-form) '((else) (endif))) + (org-edna--normalize-forms remaining-form + action-or-condition + '((else) (endif)) + from-string))) + (unless r-form (org-edna--syntax-error "Malformed if-construct; expected else or endif terminator" - from-string (cdr (car-safe r-form)))) - (setq have-else (equal (car r-form) '(else)) + from-string error-pos)) + (setq have-else (equal (caar r-form) '(else)) then-form temp-form + error-pos (cdar r-form) remaining-form (cdr r-form))) (when have-else (pcase-let* ((`(,temp-form ,r-form) - (org-edna--normalize-sexp-form remaining-form - action-or-condition - from-string))) - (unless (equal (car-safe r-form) '(endif)) + (org-edna--normalize-forms remaining-form + action-or-condition + '((endif)) + from-string))) + (unless r-form (org-edna--syntax-error "Malformed if-construct; expected endif terminator" - from-string (cdr (car-safe r-form)))) + from-string error-pos)) (setq else-form temp-form remaining-form (cdr r-form)))) (push `(if ,cond-form ,then-form ,else-form) final-form))) ((or 'then 'else 'endif) (setq need-break t) ;; Push the object back on remaining-form so the if knows where we are - (setq remaining-form (cons current-form remaining-form))) + (setq remaining-form (cons (cons current-form error-pos) remaining-form))) (_ ;; Determine the type of the form ;; If we need to change state, return from this scope @@ -291,22 +297,35 @@ the remainder of FORM after the current scope was parsed." (push '(!done?) final-form)) (list (nreverse final-form) remaining-form))) -(defun org-edna--normalize-all-forms (form-list action-or-condition &optional from-string) - "Normalize all forms in flat form list FORM-LIST. +(defun org-edna--normalize-forms (form-list action-or-condition end-forms &optional from-string) + "Normalize forms in flat form list FORM-LIST until one of END-FORMS is found. ACTION-OR-CONDITION is either 'action or 'condition, indicating which of the two types is allowed in FORM. FROM-STRING is used internally, and is non-nil if FORM was -originally a string." +originally a string. + +END-FORMS is a list of forms. When one of them is found, stop parsing." (pcase-let* ((`(,final-form ,rem-form) (org-edna--normalize-sexp-form form-list action-or-condition from-string))) (setq final-form (list final-form)) - (while rem-form + ;; Use car-safe to catch r-form = nil + (while (and rem-form (not (member (car (car-safe rem-form)) end-forms))) (pcase-let* ((`(,new-form ,r-form) (org-edna--normalize-sexp-form rem-form action-or-condition from-string))) (setq final-form (append final-form (list new-form)) rem-form r-form))) - final-form)) + (list final-form rem-form))) + +(defun org-edna--normalize-all-forms (form-list action-or-condition &optional from-string) + "Normalize all forms in flat form list FORM-LIST. + +ACTION-OR-CONDITION is either 'action or 'condition, indicating +which of the two types is allowed in FORM. + +FROM-STRING is used internally, and is non-nil if FORM was +originally a string." + (car-safe (org-edna--normalize-forms form-list action-or-condition nil from-string))) (defun org-edna-string-form-to-sexp-form (string-form action-or-condition) "Parse string form STRING-FORM into an Edna sexp form. diff --git a/org-edna.info b/org-edna.info index 25a9421..33b6708 100644 --- a/org-edna.info +++ b/org-edna.info @@ -1523,9 +1523,14 @@ File: org-edna.info, Node: 10beta6, Next: 10beta5, Up: Changelog 1.0beta6 ======== +Lots of parsing fixes. + • Fixed error reporting + • Fixed parsing of negations in conditions + • Fixed parsing of multiple forms inside if/then/else blocks + File: org-edna.info, Node: 10beta5, Next: 10beta4, Prev: 10beta6, Up: Changelog @@ -1667,10 +1672,10 @@ Node: Development43742 Node: Documentation44895 Node: Changelog45340 Node: 10beta645548 -Node: 10beta545716 -Node: 10beta446103 -Node: 10beta346356 -Node: 10beta246795 +Node: 10beta545808 +Node: 10beta446195 +Node: 10beta346448 +Node: 10beta246887 End Tag Table diff --git a/org-edna.org b/org-edna.org index 0676a31..e3422de 100644 --- a/org-edna.org +++ b/org-edna.org @@ -1292,8 +1292,13 @@ making any changes: :DESCRIPTION: List of changes by version :END: ** 1.0beta6 +Lots of parsing fixes. + - Fixed error reporting + - Fixed parsing of negations in conditions + +- Fixed parsing of multiple forms inside if/then/else blocks ** 1.0beta5 Some new forms and a new build system.