diff --git a/lib/hyperscript/parser.sx b/lib/hyperscript/parser.sx index 1e3d79dc..1a263ece 100644 --- a/lib/hyperscript/parser.sx +++ b/lib/hyperscript/parser.sx @@ -931,13 +931,29 @@ (left) (cond ((match-kw "and") - (let - ((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom))))))) - (parse-logical (list (quote and) left right)))) + (do + (when + (and + (list? left) + (> (len left) 0) + (= (first left) (quote or))) + (error + "You must parenthesize logical operations with different operators")) + (let + ((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom))))))) + (parse-logical (list (quote and) left right))))) ((match-kw "or") - (let - ((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom))))))) - (parse-logical (list (quote or) left right)))) + (do + (when + (and + (list? left) + (> (len left) 0) + (= (first left) (quote and))) + (error + "You must parenthesize logical operations with different operators")) + (let + ((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom))))))) + (parse-logical (list (quote or) left right))))) (true left)))) (define parse-expr @@ -2156,6 +2172,27 @@ (= val "%"))) (and (= typ "keyword") (= val "mod"))) (do + (when + (and (list? left) (> (len left) 0)) + (let + ((left-op (first left))) + (when + (or + (and + (or (= left-op (quote +)) (= left-op (quote -))) + (or + (= val "*") + (= val "/") + (= val "%") + (= val "mod"))) + (and + (or + (= left-op (quote *)) + (= left-op (quote /)) + (= left-op (make-symbol "%"))) + (or (= val "+") (= val "-")))) + (error + "You must parenthesize math operations with different operators")))) (adv!) (let ((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%"))))) diff --git a/shared/static/wasm/sx/hs-parser.sx b/shared/static/wasm/sx/hs-parser.sx index 4b92f1dd..1a263ece 100644 --- a/shared/static/wasm/sx/hs-parser.sx +++ b/shared/static/wasm/sx/hs-parser.sx @@ -71,12 +71,14 @@ ((typ (tp-type)) (val (tp-val))) (cond ((or (= typ "ident") (= typ "keyword")) - (do (adv!) (parse-prop-chain (list (quote .) owner val)))) + (do + (adv!) + (parse-prop-chain (list (quote poss) owner val)))) ((= typ "attr") (do (adv!) (list (quote attr) val owner))) ((= typ "class") (let ((prop (get (adv!) "value"))) - (parse-prop-chain (list (quote .) owner prop)))) + (parse-prop-chain (list (quote poss) owner prop)))) ((= typ "style") (do (adv!) (list (quote style) val owner))) (true owner))))) (define @@ -116,7 +118,18 @@ (prev-end) base-line {:root base}))) - base))))) + (if + (and + (= (tp-type) "op") + (= (tp-val) "'s") + (not (at-end?))) + (let + ((poss-prop (begin (adv!) (tp-val)))) + (do + (adv!) + (parse-prop-chain + (list (make-symbol "poss") base poss-prop)))) + base)))))) (define parse-trav (fn @@ -429,8 +442,7 @@ (let ((name val) (args (parse-call-args))) (cons (quote call) (cons (list (quote ref) name) args))))) - ((= typ "keyword") - (do (adv!) (list (quote ref) val))) + ((= typ "keyword") (do (adv!) (list (quote ref) val))) (true nil))))) (define parse-poss @@ -443,10 +455,13 @@ ((= (tp-type) "dot") (do (adv!) - (let ((typ2 (tp-type)) (val2 (tp-val))) + (let + ((typ2 (tp-type)) (val2 (tp-val))) (if (or (= typ2 "ident") (= typ2 "keyword")) - (do (adv!) (parse-poss (list (make-symbol ".") obj val2))) + (do + (adv!) + (parse-poss (list (make-symbol ".") obj val2))) obj)))) ((= (tp-type) "paren-open") (let @@ -916,13 +931,29 @@ (left) (cond ((match-kw "and") - (let - ((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom))))))) - (parse-logical (list (quote and) left right)))) + (do + (when + (and + (list? left) + (> (len left) 0) + (= (first left) (quote or))) + (error + "You must parenthesize logical operations with different operators")) + (let + ((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom))))))) + (parse-logical (list (quote and) left right))))) ((match-kw "or") - (let - ((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom))))))) - (parse-logical (list (quote or) left right)))) + (do + (when + (and + (list? left) + (> (len left) 0) + (= (first left) (quote and))) + (error + "You must parenthesize logical operations with different operators")) + (let + ((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom))))))) + (parse-logical (list (quote or) left right))))) (true left)))) (define parse-expr @@ -1475,7 +1506,8 @@ ((match-kw "to") (let ((value (parse-expr))) - (if (and (list? tgt) (= (first tgt) (quote query))) + (if + (and (list? tgt) (= (first tgt) (quote query))) (list (quote set-el!) tgt value) (list (quote set!) tgt value)))) ((match-kw "on") @@ -2140,6 +2172,27 @@ (= val "%"))) (and (= typ "keyword") (= val "mod"))) (do + (when + (and (list? left) (> (len left) 0)) + (let + ((left-op (first left))) + (when + (or + (and + (or (= left-op (quote +)) (= left-op (quote -))) + (or + (= val "*") + (= val "/") + (= val "%") + (= val "mod"))) + (and + (or + (= left-op (quote *)) + (= left-op (quote /)) + (= left-op (make-symbol "%"))) + (or (= val "+") (= val "-")))) + (error + "You must parenthesize math operations with different operators")))) (adv!) (let ((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%"))))) @@ -2648,7 +2701,14 @@ ((and (= typ "keyword") (= val "answer")) (do (adv!) (parse-answer-cmd))) ((and (= typ "keyword") (= val "settle")) - (do (adv!) (list (quote settle)))) + (do + (adv!) + (let + ((tgt (cond ((at-end?) nil) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "when") (= (tp-val) "on"))) nil) (true (parse-expr))))) + (if + (nil? tgt) + (list (quote settle)) + (list (quote settle) tgt))))) ((and (= typ "keyword") (= val "go")) (do (adv!) (parse-go-cmd))) ((and (= typ "keyword") (= val "return")) @@ -2716,9 +2776,11 @@ (adv!) (expect-kw! "view") (expect-kw! "transition") - (let ((using (if (match-kw "using") (parse-expr) nil))) + (let + ((using (if (match-kw "using") (parse-expr) nil))) (match-kw "then") - (let ((body (parse-cmd-list))) + (let + ((body (parse-cmd-list))) (match-kw "end") (list (quote view-transition!) using body))))) (true (parse-expr)))))) @@ -2882,7 +2944,11 @@ (true nil)))) (true nil)))) (consume-having!) - (when (and (= (tp-type) "keyword") (= (tp-val) "queue")) (do (adv!) (adv!))) + (when + (and + (= (tp-type) "keyword") + (= (tp-val) "queue")) + (do (adv!) (adv!))) (let ((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil))) (let diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index 5b7945d8..7948bdac 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -1805,9 +1805,11 @@ ;; ── core/parser (14 tests) ── (defsuite "hs-upstream-core/parser" (deftest "_hyperscript() evaluate API still throws on first error" - (error "SKIP (untranslated): _hyperscript() evaluate API still throws on first error")) + (assert-throws (fn () (eval-hs "add - to"))) + ) (deftest "basic parse error messages work" - (error "SKIP (untranslated): basic parse error messages work")) + (assert-throws (fn () (eval-hs "add - to"))) + ) (deftest "can have alternate comments in attributes" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) @@ -2090,7 +2092,8 @@ (assert= (dom-text-content (dom-query-by-id "div1")) "foo") )) (deftest "extra chars cause error when evaling" - (error "SKIP (untranslated): extra chars cause error when evaling")) + (assert-throws (fn () (eval-hs "1!"))) + ) (deftest "listen for event on form" (hs-cleanup!) (let ((_el-form (dom-create-element "form")) (_el-b1 (dom-create-element "button"))) @@ -3927,7 +3930,8 @@ (dom-append (dom-body) _el-button) (hs-activate! _el-button) (dom-dispatch _el-button "click" nil) - (assert= (dom-text-content (dom-query-by-id "target")) "new"))) + (assert= (dom-text-content (dom-query-by-id "target")) "new") + )) (deftest "set #id replaces element with HTML string" (hs-cleanup!) (let ((_el-target (dom-create-element "div")) (_el-button (dom-create-element "button"))) @@ -5776,11 +5780,28 @@ (assert= (eval-hs "true and (false or true)") true) ) (deftest "should short circuit with and expression" - (error "SKIP (untranslated): should short circuit with and expression")) + (let ((func1-called false) (func2-called false)) + (let ((func1 (fn () (let ((dummy (set! func1-called true))) false))) + (func2 (fn () (let ((dummy (set! func2-called true))) false)))) + (let ((result (eval-hs-locals "func1() and func2()" + (list (list (quote func1) func1) (list (quote func2) func2))))) + (assert= result false) + (assert func1-called) + (assert (not func2-called))))) + ) (deftest "should short circuit with or expression" - (error "SKIP (untranslated): should short circuit with or expression")) + (let ((func1-called false) (func2-called false)) + (let ((func1 (fn () (let ((dummy (set! func1-called true))) true))) + (func2 (fn () (let ((dummy (set! func2-called true))) true)))) + (let ((result (eval-hs-locals "func1() or func2()" + (list (list (quote func1) func1) (list (quote func2) func2))))) + (assert result) + (assert func1-called) + (assert (not func2-called))))) + ) (deftest "unparenthesized expressions with multiple operators cause an error" - (error "SKIP (untranslated): unparenthesized expressions with multiple operators cause an error")) + (assert-throws (fn () (eval-hs "true and false or true"))) + ) ) ;; ── expressions/mathOperator (15 tests) ── @@ -5827,7 +5848,8 @@ (assert= (eval-hs "1 - 1") 0) ) (deftest "unparenthesized expressions with multiple operators cause an error" - (error "SKIP (untranslated): unparenthesized expressions with multiple operators cause an error")) + (assert-throws (fn () (eval-hs "1 + 2 * 3"))) + ) ) ;; ── expressions/no (9 tests) ── @@ -6749,7 +6771,12 @@ (assert= (eval-hs-locals "`https://${foo}`" (list (list (quote foo) "bar"))) "https://bar") ) (deftest "should handle strings with tags and quotes" - (error "SKIP (untranslated): should handle strings with tags and quotes")) + (let ((record {:name "John Connor" :age 21 :favouriteColour "bleaux"})) + (assert= (eval-hs-locals + "`