diff --git a/scala-mode-fontlock.el b/scala-mode-fontlock.el
index 7617259..9f3eaf9 100644
--- a/scala-mode-fontlock.el
+++ b/scala-mode-fontlock.el
@@ -5,9 +5,10 @@
 (require 'scala-mode-syntax)
 
 (defcustom scala-font-lock:constant-list '()
-  "A list of strigs that should be fontified in constant
-face. This customization property takes effect only after the
-scala-mode has been reloaded."
+  "A list of strings that should be fontified in constant face.
+
+This customization property takes effect only after the `scala-mode' has been
+reloaded."
   :type '(repeat string)
   :group 'scala)
 
@@ -22,8 +23,6 @@ scala-mode has been reloaded."
   (when (re-search-forward scala-syntax:reserved-symbol-underscore-re limit t)
       (goto-char (match-end 2)))) ;; step back to the match (re matches futher)
 
-;(defun scala-font-lock:extend-region-function ()
-
 (defun scala-font-lock:limit-pattern2 (&optional start)
   (save-excursion
     (when start (goto-char start))
@@ -363,20 +362,9 @@ Does not continue past limit.
               "\\)")
      2 font-lock-type-face)
 
-    ;; ;; extends, with, new
-    ;; (,(concat "\\<\\(extends\\|with\\|new\\)[ \t]+\\([("
-    ;;           scala-syntax:id-first-char-group "]\\)")
-    ;;  (scala-font-lock:mark-simpleType (scala-font-lock:limit-simpleType
-    ;;                                    (goto-char (match-beginning 2)))
-    ;;                                   nil
-    ;;                                   (0 font-lock-type-face nil t)))
-
-    ;; ;; ':'
-    ;; (,scala-syntax:colon-re
-    ;;  (scala-font-lock:mark-simpleType (scala-font-lock:limit-simpleType
-    ;;                                    (goto-char (match-end 2)))
-    ;;                                   nil
-    ;;                                   (0 font-lock-type-face nil t)))
+    ;; TODO extends, with, new
+
+    ;; TODO ':'
 
     ;; def
     (,(concat "\\<def[ \t]+\\(" scala-syntax:id-re "\\)") 1 font-lock-function-name-face)
diff --git a/scala-mode-indent.el b/scala-mode-indent.el
index 486ae65..2d382c2 100644
--- a/scala-mode-indent.el
+++ b/scala-mode-indent.el
@@ -87,11 +87,11 @@ val x = if (foo)
   :group 'scala)
 
 (defconst scala-indent:eager-strategy 0
-  "See 'scala-indent:run-on-strategy'")
+  "See `scala-indent:run-on-strategy'")
 (defconst scala-indent:operator-strategy 1
-  "See 'scala-indent:run-on-strategy'")
+  "See `scala-indent:run-on-strategy'")
 (defconst scala-indent:reluctant-strategy 2
-  "See 'scala-indent:run-on-strategy'")
+  "See `scala-indent:run-on-strategy'")
 (defconst scala-indent:keywords-only-strategy 3
   "A strategy used internally by indent engine")
 
@@ -133,6 +133,14 @@ Scaladoc behavior of indenting comment lines to the second asterisk."
   :safe #'booleanp
   :group 'scala)
 
+(defcustom scala-indent:use-cycle-indent nil
+  "When non-nil, indentation will cycle from the new indent
+  strategy indent, the last known indent, and the left margin on
+  subsequent indent-line calls."
+  :type 'boolean
+  :safe #'booleanp
+  :group 'scala)
+
 (defun scala-indent:run-on-strategy ()
   "Returns the currently effecti run-on strategy"
   (or scala-indent:effective-run-on-strategy
@@ -179,7 +187,7 @@ it is nilled."
 (defun scala-indent:backward-sexp-to-beginning-of-line ()
   "Skip sexps backwards until reaches beginning of line (i.e. the
 point is at the first non whitespace or comment character). It
-does not move outside enclosin list. Returns the current point or
+does not move outside enclosing list. Returns the current point or
 nil if the beginning of line could not be reached because of
 enclosing list."
   (let ((code-beg (scala-lib:point-after
@@ -196,17 +204,16 @@ enclosing list."
       (point))))
 
 (defun scala-indent:align-anchor ()
-  "Go to beginning of line, if a) scala-indent:align-parameters
-is nil or backward-sexp-to-beginning-of-line is non-nil. This has
-the effect of staying within lists if
-scala-indent:align-parameters is non-nil."
+  "Go to beginning of line, if a) `scala-indent:align-parameters' is nil or
+`scala-indent:backward-sexp-to-beginning-of-line' is non-nil. This has the
+effect of staying within lists if `scala-indent:align-parameters' is non-nil."
   (when (or (scala-indent:backward-sexp-to-beginning-of-line)
             (not scala-indent:align-parameters))
     (back-to-indentation)))
 
 (defun scala-indent:value-expression-lead (start anchor &optional not-block-p)
-  ;; calculate an indent lead. The lead is one indent step if there is
-  ;; a '=' between anchor and start, otherwise 0.
+  ;; calculate an indent lead. The lead is one indent step if there is a '='
+  ;; between anchor and start, otherwise 0.
   (if (and scala-indent:indent-value-expression
            (ignore-errors
              (save-excursion
@@ -222,18 +229,17 @@ scala-indent:align-parameters is non-nil."
 ;;;
 
 (defconst scala-indent:mustNotTerminate-keywords-re
-  (regexp-opt '("extends" "forSome" "match" "with") 'words)
-  "Some keywords which occure only in the middle of an
-expression")
+  (regexp-opt '("extends" "match" "with") 'words)
+  "Some keywords which occur only in the middle of an expression")
 
 (defconst scala-indent:mustNotTerminate-line-beginning-re
   (concat "\\(" scala-indent:mustNotTerminate-keywords-re
           "\\|:\\("  scala-syntax:after-reserved-symbol-re "\\)\\)")
-  "All keywords and symbols that cannot terminate a expression
+  "All keywords and symbols that cannot terminate an expression
 and must be handled by run-on. Reserved-symbols not included.")
 
 (defconst scala-indent:mustTerminate-re
-  (concat "\\([,;\u21D2]\\|=>?" scala-syntax:end-of-code-line-re
+  (concat "\\([,;]\\|=>?" scala-syntax:end-of-code-line-re
           "\\|\\s(\\|" scala-syntax:empty-line-re "\\)")
   "Symbols that must terminate an expression or start a
 sub-expression, i.e the following expression cannot be a
@@ -244,15 +250,17 @@ and the empty line")
   (regexp-opt '("abstract" "catch" "case" "class" "def" "do" "else" "final"
                 "finally" "for" "if" "implicit" "import" "lazy" "new" "object"
                 "override" "package" "private" "protected" "return" "sealed"
-                "throw" "trait" "try" "type" "val" "var" "while" "yield" "inline")
+                "throw" "trait" "try" "type" "val" "var" "while" "yield" "inline"
+                "extension"
+                )
               'words)
   "Words that we don't want to continue the previous line")
 
 (defconst scala-indent:mustBeContinued-line-end-re
   (concat "\\(" scala-syntax:other-keywords-unsafe-re
           "\\|:" scala-syntax:end-of-code-line-re "\\)")
-  "All keywords and symbols that cannot terminate a expression
-and are infact a sign of run-on. Reserved-symbols not included.")
+  "All keywords and symbols that cannot terminate an expression
+and are in fact a sign of run-on. Reserved-symbols not included.")
 
 (defun scala-indent:run-on-p (&optional point strategy)
   "Returns t if the current point is in the middle of an expression"
@@ -261,9 +269,8 @@ and are infact a sign of run-on. Reserved-symbols not included.")
   (save-excursion
     (when point (goto-char point))
     (unless (eobp)
-      ;; Note: ofcourse this 'cond' could be written as one big boolean
-      ;; expression, but I doubt that would be so readable and
-      ;; maintainable
+      ;; NOTE: of course this 'cond' could be written as one big boolean
+      ;; expression, but I doubt that would be so readable and maintainable
       (cond
        ;; NO: this line starts with close parenthesis
        ((= (char-syntax (char-after)) ?\))
@@ -284,8 +291,10 @@ and are infact a sign of run-on. Reserved-symbols not included.")
        ((looking-at scala-indent:mustNotContinue-re)
         nil)
        ;; NO: this line is the start of value body
-       ((scala-indent:body-p)
-        nil)
+       ;; ((scala-indent:body-p) ;; TODO did I delete this function when I shouldn't have?
+       ;; TODO or even if I did, maybe it just doesn't matter because the
+       ;; heuristics that union this algorithm with the other will compensate?
+       ;;  nil)
        ;; YES: eager strategy can stop here, everything is a run-on if no
        ;; counter evidence
        ((= strategy scala-indent:eager-strategy)
@@ -358,46 +367,17 @@ is not on a run-on line."
     (point)))
 
 (defconst scala-indent:double-indent-re
-  (concat (regexp-opt '("with" "extends" "forSome") 'words)
+  ;; used to include with but given...with is a counterexample
+  (concat (regexp-opt '("extends" "forSome") 'words)
           "\\|:\\("  scala-syntax:after-reserved-symbol-re "\\)"))
 
-(defun scala-indent:resolve-run-on-step (start &optional anchor)
-  "Resolves the appropriate indent step for run-on line at position
-'start'"
-  (save-excursion
-    (goto-char anchor)
-    (if (scala-syntax:looking-at-case-p)
-        ;; case run-on lines get double indent, except '|' which get
-        ;; special indents
-        (progn (goto-char start)
-               (- (* 2 scala-indent:step)
-                  (skip-chars-forward "|")))
-      (goto-char start)
-      (cond
-       ;; some keywords get double indent
-       ((or (looking-at scala-indent:double-indent-re)
-            (scala-syntax:looking-back-token scala-indent:double-indent-re))
-        (* 2 scala-indent:step))
-       ;; no indent if the previous line is just close parens
-       ;; ((save-excursion
-       ;;    (scala-syntax:skip-backward-ignorable)
-       ;;    (let ((end (point)))
-       ;;      (scala-syntax:beginning-of-code-line)
-       ;;      (skip-syntax-forward ")")
-       ;;      (= (point) end)))
-       ;;  0)
-       ;; else normal indent
-       (t (+ (if scala-indent:align-parameters 0
-               (scala-indent:value-expression-lead start anchor))
-             scala-indent:step))))))
-
 (defconst scala-indent:forms-align-re
-  (regexp-opt '("yield" "else" "catch" "finally") 'words))
+  (regexp-opt '("do" "yield" "then" "else" "catch" "finally") 'words))
 
 (defun scala-indent:forms-align-p (&optional point)
-  "Returns scala-syntax:beginning-of-code-line for the line on
+  "Returns `scala-syntax:beginning-of-code-line' for the line on
 which current point (or point 'point') is, if the line starts
-with one of 'yield', 'else', 'catch' and 'finally', otherwise
+with one of 'yield', 'then', 'else', 'catch' and 'finally', otherwise
 nil. Also, the previous line must not be with '}'"
   (save-excursion
     (when point (goto-char point))
@@ -406,90 +386,6 @@ nil. Also, the previous line must not be with '}'"
       (goto-char (match-beginning 0))
       (point))))
 
-
-(defun scala-indent:goto-forms-align-anchor (&optional point)
-  "Moves back to the point whose column will be used as the
-anchor relative to which indenting of special words on beginning
-of the line on which point (or point 'point') is, or nul if not
-special word found. Special words include 'yield', 'else',
-'catch' and 'finally'"
-  (let ((special-beg (scala-indent:forms-align-p point)))
-    (when special-beg
-      (goto-char special-beg)
-      (if (and (scala-syntax:looking-back-token "}")
-               (save-excursion
-                 (goto-char (match-beginning 0))
-                 (= (match-beginning 0) (scala-lib:point-after (scala-syntax:beginning-of-code-line)))))
-          (goto-char (match-beginning 0))
-        (let ((anchor
-               (cond ((looking-at "\\<yield\\>")
-                      ;; align with 'for'
-                      (if (scala-syntax:search-backward-sexp "\\<for\\>")
-                          (point)
-                        (message "matching 'for' not found")
-                        nil))
-                     ((looking-at "\\<else\\>")
-                      ;; align with 'if' or 'else if'
-                      (if (scala-syntax:search-backward-sexp "\\<if\\>")
-                          (if (scala-syntax:looking-back-token "\\<else\\>")
-                              (goto-char (match-beginning 0))
-                            (point))
-                        nil))
-                     ((looking-at "\\<catch\\>")
-                      ;; align with 'try'
-                      (if (scala-syntax:search-backward-sexp "\\<try\\>")
-                          (point)
-                        (message "matching 'try' not found")
-                        nil))
-                     ((looking-at "\\<finally\\>")
-                      ;; align with 'try'
-                      (if (scala-syntax:search-backward-sexp "\\<try\\>")
-                          (point)
-                        (message "matching 'try' not found")
-                        nil)))))
-          (if scala-indent:align-forms
-              anchor
-            (when anchor
-              (scala-indent:align-anchor)
-              (point))))))))
-
-(defun scala-indent:resolve-forms-align-step (start anchor)
-  (if scala-indent:align-forms
-      0
-    (scala-indent:value-expression-lead start anchor t)))
-
-;;;
-;;; Lists and enumerators
-;;;
-
-(defun scala-indent:goto-list-anchor-impl (point)
-  (goto-char point)
-  ;; find the first element of the list
-  (if (not scala-indent:align-parameters)
-      (progn (back-to-indentation) (point))
-    (forward-comment (buffer-size))
-    (if (= (line-number-at-pos point)
-           (line-number-at-pos))
-        (goto-char point)
-      (beginning-of-line))
-
-    ;; align list with first non-whitespace character
-    (skip-syntax-forward " ")
-    (point)))
-
-(defun scala-indent:goto-list-anchor (&optional point)
-  "Moves back to the point whose column will be used to indent
-list rows at current point (or point `point'). Returns the new
-point or nil if the point is not in a list element > 1."
-  (let ((list-beg (scala-syntax:list-p point)))
-    (when list-beg
-      (scala-indent:goto-list-anchor-impl list-beg))))
-
-(defun scala-indent:resolve-list-step (start anchor)
-  (if scala-indent:align-parameters
-      0
-    (scala-indent:resolve-block-step start anchor)))
-
 (defun scala-indent:for-enumerators-p (&optional point)
   "Returns the point after opening parentheses if the current
 point (or point 'point') is in a block of enumerators. Return nil
@@ -508,104 +404,402 @@ if not in a list of enumerators or at the first enumerator."
             (when (< (point) point)
               (1+ (nth 1 state)))))))))
 
-(defun scala-indent:goto-for-enumerators-anchor (&optional point)
-  "Moves back to the point whose column will be used to indent
-for enumerator at current point (or point 'point'). Returns the new
-point or nil if the point is not in a enumerator element > 1."
-  (let ((enumerators-beg (scala-indent:for-enumerators-p point)))
-    (when enumerators-beg
-      (scala-indent:goto-list-anchor-impl enumerators-beg))))
-
-;;;
-;;; Body
-;;;
-
-(defconst scala-indent:control-keywords-cond-re
-  (regexp-opt '("if" "while" "for") 'words)
-  "All the flow control keywords that are followed by a
-condition (or generators in the case of 'for') in parentheses.")
-
-(defconst scala-indent:control-keywords-other-re
-  (regexp-opt '("else" "do" "yield" "try" "finally" "catch") 'words)
-  "Other flow control keywords (not followed by parentheses)")
-
-(defconst scala-indent:control-keywords-re
-  (concat "\\(" scala-indent:control-keywords-cond-re
-          "\\|" scala-indent:control-keywords-other-re "\\)"))
-
-(defun scala-indent:body-p (&optional point)
-  "Returns the position of '=' symbol, or one of the
-scala-indent:control-keywords-re or
-scala-indent:control-keywords-cond-re keywords if current
-point (or point 'point) is on a line that follows said symbol or
-keyword, or nil if not."
-  (save-excursion
-    (when point (goto-char point))
-    (scala-syntax:beginning-of-code-line)
-    (or (scala-syntax:looking-back-token scala-syntax:body-start-re 3)
-        (let ((case-fold-search nil))
-          (scala-syntax:looking-back-token scala-indent:control-keywords-other-re))
-        (progn
-          ;; if, else if
-          (when (scala-syntax:looking-back-token ")" 1)
-            (goto-char (match-end 0))
-            (backward-list))
-          (when (scala-syntax:looking-back-token scala-indent:control-keywords-cond-re)
-            (goto-char (match-beginning 0))
-            (when (and (looking-at "\\<if\\>")
-                       (scala-syntax:looking-back-token "\\<else\\>"))
-              (goto-char (match-beginning 0)))
-            (when (not scala-indent:align-forms)
-              (scala-indent:align-anchor))
-            (point))))))
-
-(defun scala-indent:goto-body-anchor (&optional point)
-  (let ((declaration-end (scala-indent:body-p point)))
-    (when declaration-end
-      (goto-char declaration-end)
-      (if (let ((case-fold-search nil))
-            (looking-at scala-indent:control-keywords-re))
-          (point)
-        (when (scala-indent:backward-sexp-to-beginning-of-line)
-          (scala-indent:goto-run-on-anchor
-           nil
-           scala-indent:keywords-only-strategy))
-        (scala-indent:align-anchor)
-        (point)))))
-
-(defun scala-indent:resolve-body-step (start &optional anchor)
-  (if (and (not (= start (point-max))) (= (char-after start) ?\{))
-      0
-    (+ (scala-indent:value-expression-lead start anchor t)
-       scala-indent:step)))
-
 ;;;
 ;;; Block
 ;;;
 
 (defun scala-indent:goto-block-anchor (&optional point)
   "Moves back to the point whose column will be used as the
-anchor for calculating block indent for current point (or point
-'point'). Returns point or (point-min) if not inside a block."
-  (let ((block-beg (nth 1 (syntax-ppss
-                           (scala-lib:point-after (beginning-of-line))))))
-    (when block-beg
-      ;; check if the opening paren is the first on the line,
-      ;; if so, it is the anchor. If not, then go back to the
-      ;; start of the line
-      (goto-char block-beg)
-      (if (= (point) (scala-lib:point-after
-                      (scala-syntax:beginning-of-code-line)))
-          (point)
-        (goto-char (or (scala-syntax:looking-back-token
-                        scala-syntax:body-start-re 3)
-                       (point)))
-        (scala-syntax:backward-parameter-groups)
-        (when (scala-indent:backward-sexp-to-beginning-of-line)
-          (scala-indent:goto-run-on-anchor nil
-                                           scala-indent:keywords-only-strategy))
-        (scala-indent:align-anchor)
-        (point)))))
+anchor for calculating block indent for current point (or POINT).
+Returns point or (point-min) if not inside a block."
+  (when-let ((block-beg (nth 1 (syntax-ppss
+                                (scala-lib:point-after (beginning-of-line))))))
+    ;; Check if the opening paren is the first on the line, if so, it is the
+    ;; anchor. If not, then go back to the start of the line
+    (goto-char block-beg)
+    (if (= (point) (scala-lib:point-after
+                    (scala-syntax:beginning-of-code-line)))
+        (point)
+      (goto-char (or (scala-syntax:looking-back-token
+                      scala-syntax:body-start-re 3)
+                     (point)))
+      (scala-syntax:backward-parameter-groups)
+      (when (scala-indent:backward-sexp-to-beginning-of-line)
+        (scala-indent:goto-run-on-anchor nil
+                                         scala-indent:keywords-only-strategy))
+      (scala-indent:align-anchor)
+      (point))))
+
+(defun scala-indent:analyze-syntax-stack (stack)
+  "A kind of tokenize step of the hand-wavy parse"
+  (pcase stack
+    ;; <hitting the beginning of a block when starting in the middle> { (
+    (`(?\{) 'left-boundary) ;; too aggressive?
+    (`(?\{ ,_ . ,_) 'left-boundary)
+    ; (`(?\( ,_ . ,_) 'left-boundary)
+    ;; <dot chaining>
+    (`(?\n ?.) 'dot-chain)
+    (`(?\n ?. . ,_) 'dot-chain)
+    ;; extension
+    ;;
+    ;; FIXME: This is a hack that just checks if the previous line contains
+    ;; extension.  The check for extension should check whether this is a
+    ;; single-def extension and for balanced parentheses, etc. to determine
+    ;; whether we emit a block token.
+    ((and `(extension . ,tail) (guard (memq ?\n tail))) 'block)
+    ;; =
+    (`(= ?\n . ,_) 'decl-lhs)
+    ((and `(= ,_ . ,tail) (guard (memq ?\n tail))) 'after-decl)
+    (`(= ,_ . ,_) 'decl-inline-lhs)
+    ;; =>
+    (`(=> ?\n . ,_) 'arrow-lhs)
+    ((and `(=> ,_ . ,tail) (guard (memq ?\n tail))) 'after-arrow)
+    (`(=> ,_ . ,_) 'arrow-lhs)
+    ;; <-
+    (`(<- . ,_) 'generator)
+    ;; case
+    (`(case . ,_) 'case)
+    ;; class
+    ((and `(class . ,tail) (guard (memq ': tail))) 'block)
+    (`(class . ,_) 'decl)
+    ;; def
+    ((and `(def . ,tail) (guard (memq '= tail)))
+     (if (memq ?\n tail) 'after-decl 'block))
+    (`(def . ,_) 'decl)
+    ;; do
+    (`(do ,_ . ,_) 'block)
+    ;; else
+    (`(else ?\n . ,_) 'else-conseq)
+    (`(else) 'else)
+    (`(else . ,_) 'else-inline)
+    ;; enum
+    ((and `(enum . ,tail) (guard (memq ': tail))) 'block)
+    (`(enum . ,_) 'decl)
+    ;; final
+    (`(final) 'decl)
+    ;; for
+    (`(for) 'for-comp)
+    (`(for . ,_) 'for-body)
+    ;; given
+    (`(given . ,_) 'decl)
+    ;; if
+    (`(if ?\n . ,_) 'if-cond)
+    (`(if . ,_) 'if)
+    ;; implicit
+    (`(implicit) 'decl)
+    ;; import
+    ((and `(import . ,tail) (guard (memq ?\n tail))) 'after-decl)
+    (`(import . ,_) 'decl)
+    ;; match
+    (`(,_ match) 'match)
+    ;; object
+    ((and `(object . ,tail) (guard (memq ': tail))) 'block)
+    (`(object . ,_) 'decl)
+    ;; override
+    (`(override) 'decl)
+    ;; package
+    (`(package . ,_) 'decl)
+    ;; sealed
+    (`(sealed) 'decl)
+    ;; then
+    (`(then ?\n . ,_) 'then-conseq)
+    (`(then) 'then)
+    (`(then . ,_) 'then-inline)
+    ;; trait
+    ((and `(trait . ,tail) (guard (memq ': tail))) 'block)
+    (`(trait . ,_) 'decl)
+    ;; val
+    ((and `(val . ,tail) (guard (memq '= tail)))
+     (if (memq ?\n tail) 'after-decl 'block))
+    (`(val . ,_) 'decl)
+    ;; var
+    (`(var . ,_) 'decl)
+    ;; while
+    (`(while) 'decl)
+    ;; with
+    (`(with) 'block)
+    ;; yield
+    (`(yield . ,_) 'yield-from-comp)
+    (`(do . ,_) 'yield-from-comp)
+    ))
+
+(defun scala-indent:relative-indent-by-elem (syntax-elem)
+  "TODO document"
+  (pcase syntax-elem
+    ;; after-decl
+    (`(after-decl else) -2)
+    (`(after-decl) 0)
+    ;; arrow-lhs
+    (`(arrow-lhs) 2)
+    (`(arrow-lhs case . ,_) 0) ;; within match
+    (`(arrow-lhs dot-chain) 4)
+    (`(arrow-lhs . ,_) :maintain)
+    ;; block
+    (`(block) 2)
+    (`(block . ,_) 2)
+    ;; case
+    (`(case) :maintain)
+    (`(case case) 0) ;; e.g. in enums
+    (`(case ,_) 2)
+    ;; decl
+    (`(decl decl) 0)
+    (`(decl decl decl-inline-lhs) 0)
+    (`(decl else) -2)
+    (`(decl . ,_) 2)
+    ;; decl-lhs
+    (`(decl-lhs decl . ,_) 2)
+    (`(decl-lhs dot-chain) 4)
+    (`(dot-chain dot-chain) 0)
+    (`(decl-lhs for-comp) 0)
+    (`(decl-lhs generator) 0)
+    (`(decl-lhs yield-from-comp) -2)
+    (`(decl-lhs) 2)
+    (`(decl-lhs . ,_) 0)
+    ;; else
+    (`(else ,_) 2)
+    ;; else-conseq
+    (`(else-conseq) 2)
+    (`(else-conseq . ,_) :maintain)
+    ;; else-inline
+    (`(else-inline . ,_) 0)
+    ;; for-body
+    (`(for-body . ,_) 2)
+    ;; for-comp
+    (`(for-comp yield-from-comp) 0)
+    ;; generator
+    (`(generator yield-from-comp) -2)
+    (`(generator . ,_) 0)
+    ;; if
+    (`(if then) 0)
+    (`(if then-inline) 0)
+    (`(if . ,_) 2)
+    ;; if-cond
+    (`(if-cond then) 0)
+    (`(if-cond) 2)
+    ;; left-boundary
+    (`(left-boundary dot-chain) 4)
+    ;; match
+    (`(match case . ,_) 2)
+    ;; then
+    (`(then else) 0)
+    (`(then else-inline) 0)
+    (`(then ,_) 2)
+    ;; then-conseq
+    (`(then-conseq else) 0)
+    (`(then-conseq) 2)
+    (`(then-conseq ,_) 2)
+    ;; then-inline
+    (`(then-inline else) 0)
+    (`(then-inline else-inline) 0)
+    ;; yield-from-comp
+    (`(yield-from-comp) 0)
+    ;; <fallbacks>
+    (`(,_ then) -2)
+    (`(,_ else) -2)
+    ))
+
+(defun scala-indent:find-analysis-start (&optional point)
+  "Find a place to start tokenizing in a consistent manner"
+  (save-excursion
+    (when point (goto-char point))
+    (let (stack)
+      ;; Always look at a token on the current for starters
+      (when (> (current-indentation) (current-column))
+        (scala-syntax:forward-token))
+      (if (= (line-beginning-position) (line-end-position))
+          ;; Handle blank lines
+          (progn
+            (scala-syntax:backward-sexp-forcing)
+            (setq stack (cons ?\n stack)))
+        ;; (beginning-of-thing 'sexp) gets confused by `.'
+        (unless (looking-at-p "\\.")
+          ;; Avoid double-reading current symbol
+          (beginning-of-thing 'sexp)))
+      ;; handle the occurence of case in various contexts
+      (or (save-excursion
+            (when-let ((_ (looking-at-p (concat "case *"
+                                              scala-syntax:class-or-object-re)))
+                     (point (progn (forward-to-word 1) (point)))
+                     (class-or-object (sexp-at-point)))
+              ;; This throws away the stack we've built up above. The assumption
+              ;; here is that this case is mutually exclusive with those above.
+              (scala-indent:skip-back-over-modifiers point
+                                                     (list class-or-object))))
+       (list (point) stack)))))
+
+(defun scala-indent:analyze-context (point &optional init-stack)
+  "TODO document"
+  (save-excursion
+    (goto-char point)
+    (let (result
+          last-indentation
+          (stack init-stack))
+      (while (and (not result) (> (point) 1))
+        (setq stack
+              (if (looking-at-p "\\.")
+                  (cons ?. stack)
+                (let ((s (or (sexp-at-point) (char-after))))
+                  (backward-char)
+                  (if (looking-at-p "\\.")
+                      ;; Try hard to notice dot-chaining
+                      (cons ?. (cons s stack))
+                    (if (looking-at-p "\"")
+                        ;; A little hack in case we are inside of a string
+                        stack
+                      (forward-char)
+                      (cons s stack))))))
+        (setq result
+              (scala-indent:analyze-syntax-stack stack))
+        (when (and (not result)
+                   (save-excursion (= (point)
+                                      (scala-syntax:beginning-of-code-line))))
+          (setq stack (cons ?\n stack))
+          (setq result
+                (scala-indent:analyze-syntax-stack stack))
+          (when result
+            (setq last-indentation (current-indentation))
+            (scala-syntax:backward-sexp-forcing)))
+        (unless result
+          (setq last-indentation (current-indentation))
+          (while (looking-at-p "\\.") (backward-char))
+          ;; ")." is a funny case where we actually do want to be on the dot
+          (if (looking-at-p ")") (forward-char))
+          (scala-syntax:backward-sexp-forcing)))
+      (let* ((x (or (scala-indent:skip-back-over-modifiers (point) stack)
+                    (list (point) stack)))
+             (point (nth 0 x))
+             (stack (nth 1 x)))
+        (list result
+              (line-number-at-pos)
+              (current-indentation)
+              last-indentation
+              point
+              stack)))))
+
+(defun scala-indent:full-stmt-less-than-line (syntax-elem stopped-point)
+  (and
+   (consp syntax-elem)
+   ;; read a full statement
+   (pcase (car syntax-elem)
+     ('after-decl t)
+     ('after-arrow t))
+   (save-excursion
+     (goto-char stopped-point)
+     ;; but that statement took up less than a line
+     (> (current-column) (current-indentation)))))
+
+(defun scala-indent:continue-lookback? (syntax-elem
+                                       ctxt-line
+                                       line-no
+                                       stopped-point
+                                       end-stack)
+  (or (and (= ctxt-line line-no) (> line-no 1)
+           ;; If we keep reading for this reason, we've accepted the
+           ;; existing tokens and so need to clear the stack
+           (list syntax-elem ;; syntax-elem
+                 nil ;; stack
+                 (save-excursion ;; point
+                   (goto-char stopped-point)
+                   (scala-syntax:backward-sexp-forcing)
+                   (point))))
+      (when (scala-indent:full-stmt-less-than-line syntax-elem stopped-point)
+        ;; If we read a full statement that was only part of a line,
+        ;; drop it and try again for more context
+        (list (cdr syntax-elem) ;; syntax-elem
+              end-stack ;; restart with the existing stack
+              (save-excursion ;; point
+                (goto-char stopped-point)
+                (scala-syntax:backward-sexp-forcing)
+                (point))))
+      ;; We know we have a dot-chain, but we need to get more context to know
+      ;; how to position it
+      (when (equal syntax-elem '(dot-chain))
+        (list syntax-elem ;; syntax-elem
+              nil ;; stack
+              stopped-point ;; point
+              ))))
+
+(defun scala-indent:skip-back-over-modifiers (point stack)
+  (if-let* ((head (car stack))
+            (_ (memq head '(trait class object)))
+            (new-point point)
+            (new-sexp t)
+            (new-stack stack))
+      (save-excursion
+        (goto-char new-point)
+        (scala-syntax:backward-sexp-forcing)
+        (setq new-sexp (sexp-at-point))
+        (while (memq new-sexp
+                     '(final sealed case open abstract implicit private))
+          (setq new-point (point))
+          (setq new-stack (cons new-sexp new-stack))
+          (scala-syntax:backward-sexp-forcing)
+          (setq new-sexp (sexp-at-point)))
+        (list new-point new-stack))))
+
+(defun scala-indent:whitespace-biased-indent (&optional point)
+  "Whitespace-syntax-friendly heuristic indentation engine.
+
+The basic idea is to look back a relatively short distance (one semantic line
+back with some hand-waving) to parse the context based on a two-level
+tokenization. The parser is not anything like well-formalized, but it can start
+at an arbitrary point in the buffer, and except in pathological cases, look at
+relatively few lines in order to make a good guess; and it is tolerant to a
+certain amount of incorrect or in-progress syntactic forms."
+  (let* ((line-no
+          ;; Get the line number while taking blanks into account.  This allows
+          ;; differentiating between indenting at a blank line and re-indenting
+          ;; at the line right before it.
+          (line-number-at-pos
+           (save-excursion
+             (when point (goto-char point))
+             (point))))
+         (initResult (scala-indent:find-analysis-start point))
+         (point (car initResult))
+         (stack (cadr initResult))
+         (analysis (scala-indent:analyze-context point stack))
+         (syntax-elem (list (nth 0 analysis)))
+         (ctxt-line (nth 1 analysis))
+         (ctxt-indent (nth 2 analysis))
+         (prev-indent (nth 3 analysis))
+         (stopped-point (nth 4 analysis))
+         (end-stack (nth 5 analysis))
+         )
+    (message "analysis: %s" analysis)
+    (while (when-let ((x (scala-indent:continue-lookback?
+                        syntax-elem ctxt-line line-no stopped-point end-stack)))
+             (setq syntax-elem (nth 0 x))
+             (setq stack (nth 1 x))
+             (setq point (nth 2 x))
+             t)
+      (setq analysis (scala-indent:analyze-context point stack))
+      (setq syntax-elem
+	    (if (nth 0 analysis)
+		(cons (nth 0 analysis) syntax-elem)
+	      syntax-elem))
+      (setq ctxt-line (nth 1 analysis))
+      (setq ctxt-indent (nth 2 analysis))
+      (setq prev-indent (nth 3 analysis))
+      (let ((old-stopped-point stopped-point))
+        (setq stopped-point (nth 4 analysis))
+        (when (eq old-stopped-point stopped-point)
+          (message
+           "Whitespace-friendly indentation algorithm not making progress :(")
+          (error "Got stuck at %s" stopped-point)))
+      (setq end-stack (nth 5 analysis)))
+    (when-let ((_ (< ctxt-line line-no))
+               (relative (scala-indent:relative-indent-by-elem syntax-elem)))
+      (list (if (eq :maintain relative)
+                (current-indentation)
+              (+ (if (eq ?\n (car end-stack))
+                     ;; Oops, moved a bit too far back while determining
+                     ;; context. Don't really want to determine our indentation
+                     ;; based on the line whose newline we are looking at, but
+                     ;; rather the next one.
+                     prev-indent
+                   ctxt-indent)
+                 relative))
+            stopped-point))))
 
 (defun scala-indent:resolve-block-step (start anchor)
   "Resolves the appropriate indent step for block line at position
@@ -633,136 +827,60 @@ anchor for calculating block indent for current point (or point
      (t  (+ scala-indent:step lead)))))
 
 ;;;
-;;; Open parentheses
+;;; Indentation engine
 ;;;
 
-(defun scala-indent:open-parentheses-line-p (&optional point)
-  "Returns the position of the first character of the line,
-if the current point (or point 'point') is on a line that starts
-with an opening parentheses, or nil if not."
+(defun scala-indent:block-biased-indent (point)
+  "TODO."
   (save-excursion
     (when point (goto-char point))
-    (scala-syntax:beginning-of-code-line)
-    (if (looking-at "\\s(") (point) nil)))
-
-(defun scala-indent:goto-open-parentheses-anchor (&optional point)
-  "Moves back to the point whose column will be used as the
-anchor for calculating opening parenthesis indent for the current
-point (or point 'point'). Returns point or nil, if line does not
-start with opening parenthesis."
-  ;; There are five cases we need to consider:
-  ;; 1. curry parentheses, i.e. 2..n parentheses groups.
-  ;; 2. value body parentheses (follows '=').
-  ;; 3. parameters, etc on separate line (who would be so mad?)
-  ;; 4. non-value body parentheses (follows class, trait, new, def, etc).
-  (let ((parentheses-beg (scala-indent:open-parentheses-line-p point)))
-    (when parentheses-beg
-      (goto-char parentheses-beg)
-      (cond
-       ;; case 1
-       ((and scala-indent:align-parameters
-             (= (char-after) ?\()
-             (scala-indent:run-on-p)
-             (scala-syntax:looking-back-token ")" 1))
-        (scala-syntax:backward-parameter-groups)
-        (let ((curry-beg (point)))
-          (forward-char)
-          (forward-comment (buffer-size))
-          (if (= (line-number-at-pos curry-beg)
-                 (line-number-at-pos))
-              (goto-char curry-beg)
-            nil)))
-       ;; case 2
-       ((scala-syntax:looking-back-token "=" 1)
-        nil) ; let body rule handle it
-       ;; case 4
-       ((and (= (char-after) ?\{)
-             (scala-indent:goto-run-on-anchor
-              nil scala-indent:keywords-only-strategy)) ; use customized strategy
-        (point))
-       ;; case 3
-       ;;((scala-indent:run-on-p)
-       ;; (scala-syntax:skip-backward-ignorable)
-       ;; (back-to-indentation)
-       ;; (point))
-       (t
-        nil)
-       ))))
-
-(defun scala-indent:resolve-open-parentheses-step (start anchor)
-  "Resolves the appropriate indent step for an open paren
-anchored at 'anchor'."
-  (cond ((scala-syntax:looking-back-token ")")
-;         (message "curry")
-         0)
-        ((save-excursion
-           (goto-char anchor)
-           ;; find =
-           (scala-syntax:has-char-before ?= start))
-;         (message "=")
-         scala-indent:step)
-        (t
-;         (message "normal at %d" (current-column))
-         0)))
-
-(defun scala-indent:goto-line-comment-anchor (&optional point)
-  "Goto and return the position relative to which a line comment
-will be indented. This will be the start of the line-comment on
-previous line, if any."
-  (let ((pos (point)))
-    (when (save-excursion
-            (when point (goto-char point))
-            (when (and (looking-at "\\s *//")
-                       (not (scala-syntax:looking-back-empty-line-p))
-                       (forward-comment -1))
-              (setq pos (point))))
-      (goto-char pos))))
-
-;;;
-;;; Indentation engine
-;;;
-
-(defun scala-indent:apply-indent-rules (rule-indents &optional point)
-  "Evaluates each rule, until one returns non-nil value. Returns
-the sum of the value and the respective indent step, or nil if
-nothing was applied."
-  (when rule-indents
-    (save-excursion
-      (when point (goto-char point))
-      (let* ((pos (scala-syntax:beginning-of-code-line))
-             (rule-indent (car rule-indents))
-             (rule-statement (car rule-indent))
-             (indent-statement (cadr rule-indent))
-             (anchor (funcall rule-statement point)))
-        (if anchor
-            (progn
-              (if scala-mode:debug-messages
-                  (message "indenting acording to %s at %d for pos %d for point %s" rule-statement anchor pos point))
-              (when (/= anchor (point))
-                (error (format "Assertion error: anchor=%d, point=%d" anchor (point))))
-              (+ (current-column)
-                 (save-excursion
-                   (if (functionp indent-statement)
-                       (funcall indent-statement pos anchor)
-                     (eval indent-statement)))))
-          (scala-indent:apply-indent-rules (cdr rule-indents)))))))
+    (let* ((pos (scala-syntax:beginning-of-code-line))
+           (anchor (scala-indent:goto-block-anchor point)))
+      (when anchor
+        (when (/= anchor (point))
+          (error (format "Assertion error: anchor=%d, point=%d" anchor (point))))
+        (list
+         (+ (current-column)
+            (save-excursion
+              (scala-indent:resolve-block-step pos anchor)))
+         anchor
+        )
+        ))))
+
+(defun scala-indent:reconcile (whitespace block)
+  (let ((ws-indent (nth 0 whitespace))
+        (ws-lookback-point (nth 1 whitespace))
+        (blk-indent-point (nth 0 block))
+        (blk-lookback (nth 1 block)))
+    (cond
+     ;; Nothing to reconcile
+     ((eq ws-indent blk-indent-point) ws-indent)
+     ;; Counterintuitive as it may be, the algorithm that had to look the
+     ;; farthest back (and so has the smallest lookback point) is least likely
+     ;; to have gotten the answer right. This is because both algorithms have
+     ;; bias toward not giving up; but the more remote they get from their
+     ;; starting point, the more likely it is that they did not understand the
+     ;; local syntax, and are going to suggest a large and unpleasant change in
+     ;; indentation. Or from another perspective: we want to bias toward local
+     ;; correctness. If they stopped on the same character, then we know from
+     ;; the behavior of the block algorithm that it is a parenthetical
+     ;; character; in which case the block algorithm most likely got the right
+     ;; answer.
+     ((> ws-lookback-point blk-lookback) ws-indent)
+     (t blk-indent-point))))
 
 (defun scala-indent:calculate-indent-for-line (&optional point)
-  "Calculate the appropriate indent for the current point or the
-point 'point'. Returns the new column, or nil if the indent
-cannot be determined."
-  (or (scala-indent:apply-indent-rules
-       `((scala-indent:goto-line-comment-anchor 0)
-         (scala-indent:goto-open-parentheses-anchor scala-indent:resolve-open-parentheses-step)
-         (scala-indent:goto-for-enumerators-anchor scala-indent:resolve-list-step)
-         (scala-indent:goto-forms-align-anchor scala-indent:resolve-forms-align-step)
-         (scala-indent:goto-list-anchor scala-indent:resolve-list-step)
-         (scala-indent:goto-body-anchor scala-indent:resolve-body-step)
-         (scala-indent:goto-run-on-anchor scala-indent:resolve-run-on-step)
-         (scala-indent:goto-block-anchor scala-indent:resolve-block-step)
-     )
-       point)
-      0))
+  "Calculate the appropriate indent for the current point or POINT.
+
+Returns the new column, or nil if the indent cannot be determined."
+  (let ((whitespace (ignore-errors
+                      (scala-indent:whitespace-biased-indent point)))
+        (block (scala-indent:block-biased-indent point)))
+    (pcase (cons whitespace block)
+      (`(nil . ,x) (nth 0 x))
+      (`(,x . nil) (nth 0 x))
+      (`(,x . ,y) (scala-indent:reconcile x y)))
+   ))
 
 (defun scala-indent:indent-line-to (column)
   "Indent the line to column and move cursor to the indent
@@ -807,25 +925,96 @@ strings"
           (beginning-of-line)
           (when (looking-at "^\\s +$") (point)))))
 
-(defun scala-indent:indent-line (&optional strategy)
-  "Indents the current line."
+(defvar-local scala-indent:cycle-indent-stack (list)
+  "The automatically buffer local scala indent cycle stack.
+
+The stack is initialized as (left-margin, (current-indentation))
+when the custom var \"scala-indent:use-cycle-indent\" is non-nil
+and \"scala-indent:indent-line\" is called. Subsequent
+\"scala-indent:indent-line\" calls pop the indentation value from
+the stack, until it is empty, resetting the indentation cycle.")
+
+
+
+(defun scala-indent:cycle-indent-stack-push (indentation)
+  "Pushes an integer value onto the \"scala-indent:cycle-indent-stack\".
+
+Will fail if INDENTATION is not an integer"
+
+  (if (integerp indentation)
+      (add-to-list 'scala-indent:cycle-indent-stack indentation)
+    (error "\"scala-indent:cycle-indent-stack-push\": Invalid INDENTATION argument %s"
+	   indentation)))
+
+(defun scala-indent:cycle-indent-stack-pop ()
+  "Gets the top value of the \"scala-indent:cycle-indent-stack\" stack.
+
+ Modifies the stack in-place."
+
+  (pop (buffer-local-value 'scala-indent:cycle-indent-stack (current-buffer))))
+
+(defun scala-indent:cycle-indent-stack-depth ()
+  "The current depth of the \"scala-indent:cycle-indent-stack\" stack"
+  
+  (length (buffer-local-value 'scala-indent:cycle-indent-stack (current-buffer))))
+
+
+(defun scala-indent:cycle-indent-stack-emptyp (x)
+  "Check if the \"scala-indent:cycle-indent-stack\" is empty.
+
+Returns t if the \"scala-indent:cycle-indent-stack\" is empty,
+nil otherwise."
+
+  (= (length (buffer-local-value 'scala-indent:cycle-indent-stack (current-buffer))) 0))
+
+(defun scala-indent:cycle-indent-line (&optional strategy)
+  "Cycle scala indentation using optionally passed STRATEGY.
+
+When the \"scala-indent:cycle-indent-stack\" is empty, push 0 and
+the current indentation onto the stack, then indent according to
+the optionally passed STRATEGY.  Indent to the top of
+\"scala-indent:cycle-indent-stack\" when non-empty."
+  
+  (interactive "*")
+  (cond ((scala-indent:cycle-indent-stack-emptyp nil)
+	 (scala-indent:cycle-indent-stack-push (current-indentation))
+	 (scala-indent:cycle-indent-stack-push 0)
+	 (call-interactively 'scala-indent:strategy-indent-line t))
+	(t (scala-indent:indent-line-to (scala-indent:cycle-indent-stack-pop)))))
+
+;; the previously-named scala-indent:indent-line
+(defun scala-indent:strategy-indent-line (&optional strategy)
+  "Indent lines according to the OPTIONAL scala indentation STRATEGY."
   (interactive "*")
   (let ((state (save-excursion (syntax-ppss (line-beginning-position)))))
-    (if (not (nth 8 state)) ;; 8 = start pos of comment or string, nil if none
-        (scala-indent:indent-code-line strategy)
-      (scala-indent:indent-line-to
-       (cond ((integerp (nth 4 state))    ;; 4 = nesting level of multi-line comment
-              (scala-indent:scaladoc-indent (nth 8 state)))
-             ((eq t (nth 3 state))   ;; 3 = t for multi-line string
-              (or (save-excursion
-                    (beginning-of-line)
-                    (when (and (looking-at "\\s *|")
-                               (progn (goto-char (nth 8 state))
-                                      (looking-at "\\(\"\"\"\\)|")))
-                      (goto-char (match-end 1))
-                      (current-column)))
-                  (current-indentation)))
-             (t (current-indentation)))))))
+    (if (nth 8 state) ;; 8 = start pos of comment or string
+        (scala-indent:indent-line-to
+         (cond ((integerp (nth 4 state))    ;; 4 = nesting level of multi-line comment
+                (scala-indent:scaladoc-indent (nth 8 state)))
+               ((eq t (nth 3 state))   ;; 3 = t for multi-line string
+                (or (save-excursion
+                      (beginning-of-line)
+                      (when (and (looking-at "\\s *|")
+                                 (progn (goto-char (nth 8 state))
+                                        (looking-at "\\(\"\"\"\\)|")))
+                        (goto-char (match-end 1))
+                        (current-column)))
+                    (current-indentation)))
+               (t (current-indentation))))
+      (scala-indent:indent-code-line strategy)))
+  )
+
+(defun scala-indent:indent-line (&optional strategy)
+  "Indent the current line with cycling.
+
+If the custom var \"scala-indent:use-cycle-indent\" is non-nil,
+cycle-indent using the optionally passed STRATEGY.  Indent using
+the optionally passed STRATEGY without cycling otherwise."
+  
+  (interactive "*")
+  (if scala-indent:use-cycle-indent
+      (call-interactively 'scala-indent:cycle-indent-line t)
+    (call-interactively 'scala-indent:strategy-indent-line t)))
 
 (defun scala-indent:indent-with-reluctant-strategy ()
   (interactive "*")
@@ -856,7 +1045,7 @@ comment is outside the comment region. "
 
 (defconst scala-indent:indent-on-words-re
   (concat "^\\s *"
-          (regexp-opt '("catch" "case" "else" "finally" "yield") 'words)))
+          (regexp-opt '("catch" "case" "then" "else" "finally" "yield") 'words)))
 
 (defun scala-indent:indent-on-special-words ()
   "This function is meant to be used with post-self-insert-hook.
@@ -922,12 +1111,8 @@ of a line inside a multi-line comment "
       (insert "*")
       (scala-indent:indent-on-scaladoc-asterisk))))
 
-(defun scala-mode:indent-scaladoc-asterisk (&optional insert-space-p)
-  (message "scala-mode:indent-scaladoc-asterisk has been deprecated"))
-
-
 (defun scala-indent:fixup-whitespace ()
-  "scala-mode version of `fixup-whitespace'"
+  "`scala-mode' version of `fixup-whitespace'"
   (interactive "*")
   (save-excursion
     (delete-horizontal-space)
diff --git a/scala-mode-syntax.el b/scala-mode-syntax.el
index edab1ab..e82d12e 100644
--- a/scala-mode-syntax.el
+++ b/scala-mode-syntax.el
@@ -1,99 +1,187 @@
-;;;; scala-mode-syntax.el - Major mode for editing scala, syntax
-;;; Copyright (c) 2012 Heikki Vesalainen
+;;;; scala-mode-syntax.el - Major mode for editing Scala, syntax
+;;; Copyright (c) 2021 Heikki Vesalainen
 ;;; For information on the License, see the LICENSE file
 
-;;; Based on Scala Language Specification (SLS) Version 2.9
+;;; Based on Scala Language Specification (SLS) Version 3.0
+;;; https://dotty.epfl.ch/docs/internals/syntax.html
 
 ;;;;
 ;;;; Scala syntax regular expressions
 ;;;;
 
-;;; Based on the Scala language specification 2.9.  Note: order is not
-;;; the same as in the document, as here things are declared before
-;;; used.
+;;; Based on the Scala language specification 3.0. Note: order is not the same
+;;; as in the document, as here things are declared before used.
 
-;;; A note on naming. Things that end with '-re' are regular
-;;; expressions.  Things that end with '-group' are regular expression
-;;; character groups without the enclosing [], i.e. they are not
-;;; regular expressions, but can be used in declaring one.
+;;; A note on naming. Things that end with `-re' are regular expressions. Things
+;;; that end with `-group' are regular expression character groups without the
+;;; enclosing [], i.e. they are not regular expressions, but can be used in
+;;; declaring one.
 
-;; single letter matching groups (Chapter 1)
-(defconst scala-syntax:hexDigit-group "0-9A-Fa-f")
-(defconst scala-syntax:UnicodeEscape-re (concat "\\\\u[" scala-syntax:hexDigit-group "]\\{4\\}"))
+(defun scala-syntax:alt (&rest res)
+  (concat "\\(" (string-join res "\\|") "\\)"))
 
+;; single letter matching groups
+(defconst scala-syntax:hexDigit-group "0-9A-Fa-f")
+(defconst scala-syntax:UnicodeEscape-re
+  ;; using `format' allows editing these regexes with one step closer to a sane
+  ;; number of backslash escapes, via `string-edit', at the expense of making %
+  ;; a special character
+  (format "\\\\u[%s]\\{4\\}" scala-syntax:hexDigit-group))
+
+;; TODO BNF for `upper' adds the coments "and Unicode category Lu"; do Emacs
+;; regexes handle this naturally?
 (defconst scala-syntax:upper-group "[:upper:]\\$") ;; missing _ to make ids work
-(defconst scala-syntax:upperAndUnderscore-group (concat "_" scala-syntax:upper-group ))
+;; NOTE `upperAndUnderscore' corresponds to the `upper' group in the BNF
+(defconst scala-syntax:upperAndUnderscore-group
+  (concat "_" scala-syntax:upper-group ))
+;; TODO BNF for `lower' adds the coments "and Unicode category Ll"; do Emacs
+;; regexes handle this naturally?
 (defconst scala-syntax:lower-group "[:lower:]")
-(defconst scala-syntax:letter-group (concat scala-syntax:lower-group scala-syntax:upper-group)) ;; TODO: add Lt, Lo, Nl
+;; TODO BNF for `lower' adds the coments "and Unicode categories Lo, Lt, Nl"
+(defconst scala-syntax:letter-group (concat scala-syntax:lower-group
+                                            scala-syntax:upper-group))
 (defconst scala-syntax:digit-group "0-9")
-(defconst scala-syntax:letterOrDigit-group (concat
-                                            scala-syntax:upperAndUnderscore-group
-                                            scala-syntax:lower-group
-                                            scala-syntax:digit-group))
-(defconst scala-syntax:opchar-safe-group "!%&*+/?\\\\^|~-") ;; TODO: Sm, So
+;; NOTE `letterOrDigit' does not have a separate entry in the 3.0 BNF.
+(defconst scala-syntax:letterOrDigit-group
+  (concat
+   scala-syntax:upperAndUnderscore-group
+   scala-syntax:lower-group
+   scala-syntax:digit-group))
+;; TODO ensure Unicode Sm, So disallowed in `opchar'
+;; TODO do the math: check these positively stated symbols against the
+;; negatively stated BNF.
+(defconst scala-syntax:opchar-safe-group "!%&*+/?\\\\^|~-")
 (defconst scala-syntax:opchar-unsafe-group "#:<=>@")
 (defconst scala-syntax:opchar-group (concat scala-syntax:opchar-unsafe-group
                                             scala-syntax:opchar-safe-group))
 
-;; Scala delimiters (Chapter 1), but no quotes
+;; NOTE `delim' in the BNF
+;; TODO should backtick be here? I'm not sure it is handled correctly ATM.
+;; Scala delimiters, but no quotes
 (defconst scala-syntax:delimiter-group ".,;")
 
-;; Integer Literal (Chapter 1.3.1)
+;; NOTE BNF also has a definition here for `printableChar'
+;; `printableChar' has a definition that seems to restrict it to simple ASCII
+;; characters, though, which is surprising.
+;; We may not need it though, because e.g. in the definition of
+;; `characterLiteral' we use some faster regex.
+
+;; Escape Sequences
+(defconst scala-syntax:charEscapeSeq-re "\\\\['btnfr\"\\\\]")
+
+(defconst scala-syntax:op-re
+  (concat "[" scala-syntax:opchar-group "]+" ))
+
+(defconst scala-syntax:idrest-re
+  ;; Eagerness of regexp causes problems with _. The following is a workaround,
+  ;; but the resulting regexp matches only what SLS demands.
+  (format "\\([_]??[%s%s]+\\)*\\(_+%s\\|_\\)?"
+          scala-syntax:letter-group
+          scala-syntax:digit-group
+          scala-syntax:op-re))
+
+(defconst scala-syntax:varid-re
+  (concat "[" scala-syntax:lower-group "]" scala-syntax:idrest-re))
+
+;; `alphaid' introduced by SIP-11 - String Interpolation
+;; https://docs.scala-lang.org/sips/string-interpolation.html
+(defconst scala-syntax:alphaid-re
+  (format "\\([%s%s]%s\\)"
+          scala-syntax:lower-group
+          scala-syntax:upperAndUnderscore-group
+          scala-syntax:idrest-re))
+
+(defconst scala-syntax:plainid-re
+  (scala-syntax:alt scala-syntax:alphaid-re
+                    scala-syntax:op-re))
+
+;; NOTE `stringlit' is referred to, but not defined in the Scala Language
+;; Specification 2.9. We define it as consisting of anything but '`' and newline
+;; NOTE this is not the same thing as `stringLiteral'
+(defconst scala-syntax:stringlit-re "[^`\n\r]")
+;; NOTE there is `quoteId' but not `quotedId' in the Scala 3 BNF, and the
+;; `quoteId' is for something different.
+;; TODO The Scala 3 BNF has
+;;   { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq }
+;; rather than `stringlit'.
+(defconst scala-syntax:quotedid-re (concat "`" scala-syntax:stringlit-re "+`"))
+(defconst scala-syntax:id-re
+  (scala-syntax:alt scala-syntax:plainid-re scala-syntax:quotedid-re))
+
+(defconst scala-syntax:quoteid-re
+  (concat "'" scala-syntax:alphaid-re))
+
+;; Integer Literal
 (defconst scala-syntax:nonZeroDigit-group "1-9")
-(defconst scala-syntax:octalDigit-group "0-7")
 (defconst scala-syntax:decimalNumeral-re
   (concat "0"
-          "\\|[" scala-syntax:nonZeroDigit-group "][" scala-syntax:digit-group "]*"))
-(defconst scala-syntax:hexNumeral-re (concat "0x[" scala-syntax:hexDigit-group "]+"))
-(defconst scala-syntax:octalNumeral-re (concat "0[" scala-syntax:octalDigit-group "]+"))
+          "\\|["
+          scala-syntax:nonZeroDigit-group
+          "]\\(["
+          scala-syntax:digit-group
+          "_]*["
+          scala-syntax:digit-group
+          "]\\)?"))
+(defconst scala-syntax:hexNumeral-re
+  (concat "0[xX]["
+          scala-syntax:hexDigit-group
+          "]\\(["
+          scala-syntax:hexDigit-group
+          "_]*["
+          scala-syntax:hexDigit-group
+          "]\\)?"))
+
 (defconst scala-syntax:integerLiteral-re (concat "-?" ;; added from definition of literal
                                                  "\\(" scala-syntax:hexNumeral-re
-                                                 "\\|" scala-syntax:octalNumeral-re
                                                  "\\|" scala-syntax:decimalNumeral-re
                                                  "\\)[Ll]?"))
 
+;; Floating Point Literal
+(defconst scala-syntax:exponentPart-re
+  (concat "\\([eE][+-]?["
+          scala-syntax:digit-group
+          "_]*["
+          scala-syntax:digit-group
+          "]\\)"))
 
-;; Floating Point Literal (Chapter 1.3.2)
-(defconst scala-syntax:exponentPart-re (concat "\\([eE][+-]?[" scala-syntax:digit-group "]+\\)"))
 (defconst scala-syntax:floatType-re "[fFdD]")
+
 (defconst scala-syntax:floatingPointLiteral-re
   (concat "-?" ;; added from definition of literal
-          "\\([" scala-syntax:digit-group "]+\\.[" scala-syntax:digit-group "]*"
-          scala-syntax:exponentPart-re "?" scala-syntax:floatType-re "?"
-          "\\|" "\\.[" scala-syntax:digit-group "]+"
+          "\\(" "\\(" scala-syntax:decimalNumeral-re "\\)"
+          "\\.[" scala-syntax:digit-group "]"
+          "\\([" scala-syntax:digit-group "_]*" scala-syntax:digit-group "\\)?"
           scala-syntax:exponentPart-re "?" scala-syntax:floatType-re "?"
-          "\\|" "[" scala-syntax:digit-group "]+" scala-syntax:exponentPart-re
-          "\\|" "[" scala-syntax:digit-group "]+" scala-syntax:floatType-re "\\)"))
+          "\\|" "\\(" scala-syntax:decimalNumeral-re "\\)"
+          scala-syntax:exponentPart-re scala-syntax:floatType-re "?"
+          "\\|" "\\(" scala-syntax:decimalNumeral-re "\\)" scala-syntax:floatType-re "\\)"))
 
 (defconst scala-syntax:number-safe-start-re
   (concat "[^_" scala-syntax:letter-group "]"))
 
-;; Boolean Literals (Chapter 1.3.3)
+;; Boolean Literals
 (defconst scala-syntax:booleanLiteral-re "true|false")
 
-;; Escape Sequences (Chapter 1.3.6)
-(defconst scala-syntax:escapeSequence-re "\\\\['btnfr\"\\\\]")
-
-;; Octal Escape Sequences (Chapter 1.3.6)
-(defconst scala-syntax:octalEscape-re (concat "\\\\[" scala-syntax:octalDigit-group "\\]\\{1,3\\}"))
-
-;; Character Literals (Chapter 1.3.4)
+;; Character Literals
 (defconst scala-syntax:characterLiteral-re
+  ;; TODO Scala 3 character literal does not mention Unicode escape. Is that a
+  ;; mistake?
   (concat "\\('\\)\\(" "[^\\\\]" ;; should be just printable char, but this is faster
-          "\\|" scala-syntax:escapeSequence-re
-          "\\|" scala-syntax:octalEscape-re
+          "\\|" scala-syntax:charEscapeSeq-re
           "\\|" scala-syntax:UnicodeEscape-re "\\)\\('\\)"))
 
 (defconst scala-syntax:string-escape-re
-  (concat scala-syntax:escapeSequence-re
-          "\\|" scala-syntax:octalEscape-re
+  (concat scala-syntax:charEscapeSeq-re
           "\\|" scala-syntax:UnicodeEscape-re))
 
-;; String Literals (Chapter 1.3.5)
+;; String Literals
 (defconst scala-syntax:stringElement-re
   (concat "\\(" "[^\n\"\\\\]"
-          "\\|" scala-syntax:string-escape-re  "\\)"))
-(defconst scala-syntax:oneLineStringLiteral-re (concat "\\(\"\\)" scala-syntax:stringElement-re "*\\(\"\\)"))
+         "\\|" scala-syntax:string-escape-re  "\\)"))
+
+(defconst scala-syntax:oneLineStringLiteral-re
+  (concat "\\(\"\\)" scala-syntax:stringElement-re "*\\(\"\\)"))
+
 (defconst scala-syntax:multiLineStringLiteral-start-re
   "\\(\"\\)\"\"\\(\"?\"?[^\"]\\)*")
 (defconst scala-syntax:multiLineStringLiteral-end-re
@@ -105,47 +193,30 @@
   (concat "\\(" scala-syntax:multiLineStringLiteral-re
           "\\|" scala-syntax:oneLineStringLiteral-re "\\)" ))
 
-;; If you change this or any of the used regex, be sure to
-;; maintain this or update propertize function accordingly:
-;; group 1 = char start, 3 = char end
-;; group 4 = multi-line string start, 6 = end
-;; group 7 = string start, 9 = end
+;; If you change this or any of the used regex, be sure to maintain this or
+;; update propertize function accordingly. Start and end group numbers:
+;; |-------------------+-------+-----|
+;; | Syntax item       | Start | End |
+;; |-------------------+-------+-----|
+;; | Character         |     1 |   3 |
+;; | Multi-line string |     4 |   6 |
+;; | One-line string   |     7 |   9 |
+;; |-------------------+-------+-----|
 (defconst scala-syntax:relaxed-char-and-string-literal-re
   (concat scala-syntax:characterLiteral-re
           "\\|" scala-syntax:multiLineStringLiteral-start-re
           "\\(?:" scala-syntax:multiLineStringLiteral-end-re "\\)?"
           "\\|\\(\"\\)" "\\(\\\\.\\|[^\"\n\\]\\)*" "\\(\"\\)"))
 
-;; Identifiers (Chapter 1.1)
-(defconst scala-syntax:op-re
-  (concat "[" scala-syntax:opchar-group "]+" ))
-(defconst scala-syntax:idrest-re
-  ;; Eagerness of regexp causes problems with _. The following is a workaround,
-  ;; but the resulting regexp matches only what SLS demands.
-  (concat "\\(" "[_]??" "[" scala-syntax:letter-group scala-syntax:digit-group "]+" "\\)*"
-          "\\(" "_+" scala-syntax:op-re "\\|" "_" "\\)?"))
-(defconst scala-syntax:varid-re (concat "[" scala-syntax:lower-group "]" scala-syntax:idrest-re))
-(defconst scala-syntax:capitalid-re (concat "[" scala-syntax:upperAndUnderscore-group "]" scala-syntax:idrest-re))
-;; alphaid introduce by SIP11
-(defconst scala-syntax:alphaid-re (concat "\\(" "[" scala-syntax:lower-group scala-syntax:upperAndUnderscore-group "]" scala-syntax:idrest-re "\\)"))
-(defconst scala-syntax:plainid-re (concat "\\(" scala-syntax:alphaid-re "\\|" scala-syntax:op-re "\\)"))
-;; stringlit is referred to, but not defined Scala Language Specification 2.9
-;; we define it as consisting of anything but '`' and newline
-(defconst scala-syntax:stringlit-re "[^`\n\r]")
-(defconst scala-syntax:quotedid-re (concat "`" scala-syntax:stringlit-re "+`"))
-(defconst scala-syntax:id-re (concat "\\(" scala-syntax:plainid-re
-                              "\\|" scala-syntax:quotedid-re "\\)"))
-(defconst scala-syntax:id-first-char-group
-  (concat scala-syntax:lower-group
-          scala-syntax:upperAndUnderscore-group
-          scala-syntax:opchar-group))
-
-;; Symbol literals (Chapter 1.3.7)
+;; Symbol literals
 (defconst scala-syntax:symbolLiteral-re
-  ;; must end with non-' to not conflict with scala-syntax:characterLiteral-re
+  ;; NOTE must end with non-' to not conflict with
+  ;; `scala-syntax:characterLiteral-re'
   (concat "\\('" scala-syntax:plainid-re "\\)\\([^']\\|$\\)"))
 
-;; Literals (Chapter 1.3)
+;; Literals
+;; TODO Scala 3 distinguishes between Literal and SimpleLiteral; if that becomes
+;; important below, then distinguish here.
 (defconst scala-syntax:literal-re
   (concat "\\(" scala-syntax:integerLiteral-re
           "\\|" scala-syntax:floatingPointLiteral-re
@@ -155,6 +226,11 @@
           "\\|" scala-syntax:symbolLiteral-re
           "\\|" "null" "\\)"))
 
+;; TODO Scala 3 defines QualId, ids and SimpleRef here. SimpleRef seems to bear
+;; a relation to what was before called "path".
+
+;; TODO does not occur under this name in Scala 3 BNF; is this was what is there
+;; called escape, processedStringLiteral, processedStringPart, &c?
 (defconst scala-syntax:interpolation-re
   (concat "\\(" "\\$"  scala-syntax:id-re "\\|" "\\${[^}\n\\\\]*}" "\\)"))
 
@@ -178,18 +254,9 @@
               t))) ;; keep going
     pos))
 
-;; Paths (Chapter 3.1)
-;; emacs has a problem with these regex, don't use them
-;; (defconst scala-syntax:classQualifier-re (concat "[[]" scala-syntax:id-re "[]]"))
-;; (defconst scala-syntax:stableId-re
-;;   (concat "\\(\\(" "this"
-;;           "\\|" "super" scala-syntax:classQualifier-re
-;;           "\\|" scala-syntax:id-re
-;;           "\\)\\.\\)*"
-;;           scala-syntax:id-re))
-;; (defconst scala-syntax:path-re
-;;   (concat "\\(" scala-syntax:stableId-re
-;;           "\\|" "\\(" scala-syntax:id-re "\\." "\\)?" "this" "\\)"))
+;; NOTE Emacs has a problem with these regexes; don't use them.
+;; (defconst scala-syntax:classQualifier-re
+;;   (concat "[[]" scala-syntax:id-re "[]]"))
 
 (defun scala-syntax:looking-at-super ()
   (save-excursion
@@ -216,6 +283,8 @@
           (set-match-data `(,beg ,(match-end 0)))
           t)))))
 
+;; NOTE written as a function because Emacs struggled with the regex
+;; TODO I don't see references to "stable" identifiers in Scala 3 BNF
 (defun scala-syntax:looking-at-stableIdOrPath (&optional path-p beg)
   (unless beg (setq beg (point)))
   (save-excursion
@@ -236,21 +305,12 @@
              (set-match-data `(,beg ,(match-end 0)))
              (point))))))
 
+;; TODO update per SimplePattern in Scala 3 BNF
 (defun scala-syntax:looking-at-simplePattern-beginning ()
   (or (looking-at "[_(]")
       (looking-at scala-syntax:literal-re)
       (scala-syntax:looking-at-stableIdOrPath)))
 
-
-(defun scala-syntax:regexp-for-id (id)
-  (let ((prefix-regex
-         (if (string-match scala-syntax:alphaid-re id)
-             "\\b" (concat "\\(^\\|[^" scala-syntax:opchar-group "]\\)")))
-        (suffix-regex
-         (if (string-match scala-syntax:op-re (substring id -1 nil))
-             (concat "\\([^" scala-syntax:opchar-group "]\\|$\\)") "\\b")))
-    (concat prefix-regex id suffix-regex)))
-
 ;;;
 ;;; Other regular expressions
 ;;;
@@ -267,9 +327,11 @@
 (defconst scala-syntax:end-of-code-line-re
   (concat "\\([ ]\\|$\\|" scala-syntax:comment-start-re "\\)")
   "A special regexp that can be concatenated to an other regular
-  expression when used with scala-syntax:looking-back-token. Not
-  meaningfull in other contexts.")
+  expression when used with `scala-syntax:looking-back-token'. Not
+  meaningful in other contexts.")
 
+;; TODO there is only one reference to the word "path" in the Scala 3 BNF. Is
+;; that a vestige? Should this be renamed?
 (defconst scala-syntax:path-keywords-unsafe-re
   (regexp-opt '("super" "this") 'words))
 
@@ -305,30 +367,21 @@
           "\\|" scala-syntax:other-keywords-unsafe-re
           "\\)"))
 
-;; TODO: remove
-;; (defconst scala-syntax:keywords-re
-;;   (concat "\\(^\\|[^`'_]\\)\\(" scala-syntax:value-keywords-unsafe-re
-;;           "\\|" scala-syntax:path-keywords-unsafe-re
-;;           "\\|" scala-syntax:other-keywords-unsafe-re "\\)"))
-
-
 (defconst scala-syntax:after-reserved-symbol-underscore-re
   (concat "$\\|" scala-syntax:comment-start-re
           "\\|[^" scala-syntax:letterOrDigit-group "]"))
 
+;; Reserved symbol _
 (defconst scala-syntax:reserved-symbol-underscore-re
-  ;; reserved symbol _
   (concat "\\(^\\|[^" scala-syntax:letterOrDigit-group "]\\)"
           "\\(_\\)"
           "\\(" scala-syntax:after-reserved-symbol-underscore-re "\\)"))
 
+;; Reserved symbols. The regexp is unsafe as it does not check the context.
 (defconst scala-syntax:reserved-symbols-unsafe-re
-  ;; reserved symbols. The regexp is unsafe as it does not
-  ;; check the context.
-  "\\([:#@\u21D2\u2190]\\|=>?\\|<[:%!?\\-]\\|>:\\)" )
+  "\\([:#@]\\|=>?\\|<[:%!?\\-]\\|>:\\)" )
 
-(defconst scala-syntax:double-arrow-unsafe-re
-  "\\(=>\\|\u21D2\\)")
+(defconst scala-syntax:double-arrow-unsafe-re "=>")
 
 (defconst scala-syntax:after-reserved-symbol-re
   (concat "\\($\\|" scala-syntax:comment-start-re
@@ -336,16 +389,11 @@
 
 (defconst scala-syntax:reserved-symbols-re
   ;; reserved symbols and XML starts ('<!' and '<?')
+  ;; TODO XML starts have been dropped from the language
   (concat "\\(^\\|[^" scala-syntax:opchar-group "]\\)"
           scala-syntax:reserved-symbols-unsafe-re
           "\\(" scala-syntax:after-reserved-symbol-re "\\)"))
 
-(defconst scala-syntax:colon-re
-  (concat "\\(^\\|[^" scala-syntax:opchar-group "]\\)"
-          "\\(:\\)"
-          "\\(" scala-syntax:after-reserved-symbol-re "\\)"))
-
-
 (defconst scala-syntax:override-unsafe-re
   (regexp-opt '("override") 'words))
 
@@ -364,12 +412,14 @@
 (defconst scala-syntax:final-re
   (concat "\\(^\\|[^`'_]\\)\\(" scala-syntax:final-unsafe-re "\\)"))
 
+;; TODO open
 (defconst scala-syntax:sealed-unsafe-re
   (regexp-opt '("sealed") 'words))
 
 (defconst scala-syntax:sealed-re
   (concat "\\(^\\|[^`'_]\\)\\(" scala-syntax:sealed-unsafe-re "\\)"))
 
+;; TODO using/given
 (defconst scala-syntax:implicit-unsafe-re
   (regexp-opt '("implicit") 'words))
 
@@ -388,6 +438,7 @@
 (defconst scala-syntax:private-re
   (concat "\\(^\\|[^`'_]\\)\\(" scala-syntax:private-unsafe-re "\\)"))
 
+;; TODO isn't this being dropped?
 (defconst scala-syntax:protected-unsafe-re
   (regexp-opt '("protected") 'words))
 
@@ -395,16 +446,23 @@
   (concat "\\(^\\|[^`'_]\\)\\(" scala-syntax:protected-unsafe-re "\\)"))
 
 (defconst scala-syntax:modifiers-unsafe-re
-  (regexp-opt '("override" "abstract" "final" "sealed" "implicit" "lazy"
+  ;; TODO is `protected` deprecated? Can `given` ever be a modifier, or does it
+  ;; always eat its `def`?
+  (regexp-opt '("override" "abstract" "final" "sealed" "implicit" "lazy" "open"
                 "private" "protected") 'words))
 
 (defconst scala-syntax:modifiers-re
   (concat "\\(^\\|[^`'_]\\)\\(" scala-syntax:modifiers-unsafe-re "\\)"))
 
 (defconst scala-syntax:body-start-re
+  ;; TODO this is getting complicated in Scala 3. A block can be introduced by
+  ;; `:` or `with`, but it depends on what kind of definition it is.
   (concat "=" scala-syntax:end-of-code-line-re)
   "A regexp for detecting if a line ends with '='")
 
+;; NOTE "list" here seems to refer to the Emacs S-Expr concept of a List, not
+;; anything that is considered a list in Scala.
+;; TODO what might be new here for Scala 3?
 (defconst scala-syntax:list-keywords-re
   (regexp-opt '("var" "val" "import") 'words)
   ("Keywords that can start a list"))
@@ -418,23 +476,20 @@
 (defconst scala-syntax:class-or-object-re
   (regexp-opt '("class" "object") 'words))
 
-
 ;;;;
 ;;;; Character syntax table and related syntax-propertize functions
 ;;;;
 
-;;; The syntax table relies havily on the syntax-propertize-functions being
-;;; run. Hence this syntax requires at least emacs 24, which introduced
-;;; this new facility.
+;;; The syntax table relies havily on the `syntax-propertize-function's being
+;;; run. Hence this syntax requires at least Emacs 24.
 
 (defvar scala-syntax:syntax-table nil
   "Syntax table used in `scala-mode' buffers.")
 (when (not scala-syntax:syntax-table)
   (let ((syntab (make-syntax-table)))
-    ;; 1. start by reseting the syntax table: only (){}[] are
-    ;; parentheses, so all others marked as parentheses in the parent
-    ;; table must be marked as symbols, nothing is a punctuation
-    ;; unless otherwise stated
+    ;; Start by reseting the syntax table: only (){}[] are parentheses, so all
+    ;; others marked as parentheses in the parent table must be marked as
+    ;; symbols. Nothing is a punctuation unless otherwise stated.
     (map-char-table
      #'(lambda (key value)
          (when (or (= (syntax-class value) 4) ; open
@@ -443,11 +498,11 @@
            (modify-syntax-entry key "_" syntab)))
      (char-table-parent syntab))
 
-    ;; Below 'space', everything is either illegal or whitespace.
-    ;; Consider as whitespace, unless otherwise stated below.
+    ;; Below 'space', everything is either illegal or whitespace. Consider as
+    ;; whitespace, unless otherwise stated below.
     (modify-syntax-entry '(0 . 32) " " syntab)
 
-    ;; The scala parentheses
+    ;; The Scala parentheses
     (modify-syntax-entry ?\( "()" syntab)
     (modify-syntax-entry ?\[ "(]" syntab)
     (modify-syntax-entry ?\{ "(}" syntab)
@@ -455,44 +510,40 @@
     (modify-syntax-entry ?\] ")[" syntab)
     (modify-syntax-entry ?\} "){" syntab)
 
-    ;; _ is upper-case letter, but will be modified to be symbol
-    ;; constituent when in reserved symbol position by
-    ;; syntax-propertize-function
+    ;; _ is upper-case letter, but will be modified to be symbol constituent
+    ;; when in reserved symbol position by `syntax-propertize-function'
     (modify-syntax-entry ?\_ "w" syntab)
 
-    ;; by default all opchars are punctuation, but they will be
-    ;; modified by syntax-propertize-function to be symbol
-    ;; constituents when a part of varid or capitalid
-    (dolist (char (mapcar 'identity "!#%&*+/:<=>?@^|~-\u21D2\u2190")) ;; TODO: Sm, So
+    ;; By default all opchars are punctuation, but they will be modified by
+    ;; `syntax-propertize-function' to be symbol constituents when a part of
+    ;; varid or capitalid ;; TODO capitalid not mentioned in Scala 3 BNF
+    (dolist (char (mapcar #'identity "!#%&*+/:<=>?@^|~-")) ;; TODO: Sm, So
       (modify-syntax-entry char "." syntab))
 
-    ;; for clarity, the \ is alone here and not in the string above
+    ;; For clarity, the \ is alone here and not in the string above
     (modify-syntax-entry ?\\ "." syntab)
 
-    ;; scala strings cannot span lines, so we mark
-    ;; " as punctuation, but do the real stuff
-    ;; in syntax-propertize-function for properly
-    ;; formatted strings.
+    ;; Scala strings cannot span lines, so we mark " as punctuation, but do the
+    ;; real stuff in `syntax-propertize-function' for properly formatted
+    ;; strings.
     (modify-syntax-entry ?\" "." syntab)
 
-    ;; backquote is given paired delimiter syntax so that
-    ;; quoted ids are parsed as one sexp. Fontification
-    ;; is done separately.
+    ;; Backquote is given paired delimiter syntax so that quoted ids are parsed
+    ;; as one S-expression. Fontification is done separately.
     (modify-syntax-entry ?\` "$" syntab)
 
-    ;; ' is considered an expression prefix, since it can
-    ;; both start a Symbol and is a char quote. It
-    ;; will be given string syntax by syntax-propertize-function
-    ;; for properly formatted char literals.
+    ;; ' is considered an expression prefix, since it can both start a Symbol
+    ;; and is a char quote. It will be given string syntax by
+    ;; `syntax-propertize-function' for properly formatted char literals.
     (modify-syntax-entry ?\' "'" syntab)
 
-    ;; punctuation as specified by SLS
+    ;; Punctuation as specified by SLS
     (modify-syntax-entry ?\. "." syntab)
     (modify-syntax-entry ?\; "." syntab)
     (modify-syntax-entry ?\, "." syntab)
 
-    ;; comments
-    ;; the `n' means that comments can be nested
+    ;; Comments
+    ;; The `n' means that comments can be nested
     (modify-syntax-entry ?\/  ". 124b" syntab)
     (modify-syntax-entry ?\*  ". 23n"   syntab)
     (modify-syntax-entry ?\n  "> b" syntab)
@@ -501,26 +552,25 @@
     (setq scala-syntax:syntax-table syntab)))
 
 (defun scala-syntax:propertize-extend-region (start end)
-  "See syntax-propertize-extend-region-functions"
+  "See `syntax-propertize-extend-region-functions'"
   ;; nothing yet
   nil)
 
 (defmacro scala-syntax:put-syntax-table-property (match-group value)
-  "Add 'syntax-table entry 'value' to the region marked by the
-match-group 'match-group'"
+  "Add `syntax-table' entry VALUE to the region marked by MATCH-GROUP"
   `(put-text-property (match-beginning ,match-group)
                       (match-end ,match-group)
                       'syntax-table
                       ,value))
 
 (defun scala-syntax:propertize-char-and-string-literals (start end)
-  "Mark start and end of character literals as well as one-line
-and multi-line string literals. One-line strings and characters
-use syntax class 7 (string quotes), while multi-line strings are
-marked with 15 (generic string delimiter). Multi-line string
-literals are marked even if they are unbalanced. One-line string
-literals have to be balanced to get marked. This means invalid
-characters and one-line strings will not be fontified."
+  "Mark start and end of character and string literals.
+
+One-line strings and characters use syntax class 7 (string quotes), while
+multi-line strings are marked with 15 (generic string delimiter). Multi-line
+string literals are marked even if they are unbalanced. One-line string literals
+have to be balanced to get marked. This means invalid characters and one-line
+strings will not be fontified."
 
   (let* ((string-state (nth 3 (syntax-ppss start)))
          (unbalanced-p (eq string-state t)))
@@ -579,6 +629,7 @@ characters and one-line strings will not be fontified."
       (when (re-search-forward "\n" end t)
         (scala-syntax:put-syntax-table-property 0 '(12 . nil))))))
 
+;; TODO what makes underscores to be considered uppercase?
 (defun scala-syntax:propertize-underscore-and-idrest (start end)
   "Mark all underscores (_) as symbol constituents (syntax 3) or
 upper case letter (syntax 2). Also mark opchars in idrest as
@@ -614,7 +665,8 @@ symbol constituents (syntax 3)."
         (unless (or
                  (string-suffix-p "*/" match)
                  (member match '("</"))
-                 (member 0 (mapcar (lambda (regexp) (string-match regexp match)) '("^*+/$" "^//.*$" "^/\\*+$")))
+                 (member 0 (mapcar (lambda (regexp) (string-match regexp match))
+                                   '("^*+/$" "^//.*$" "^/\\*+$")))
                  (equal 2 (syntax-class (syntax-after match-end)))
                  (equal 2 (syntax-class (syntax-after (1- match-beg)))))
           (put-text-property match-beg match-end 'syntax-table '(3 . nil)))))))
@@ -634,7 +686,7 @@ symbol constituents (syntax 3)."
       (scala-syntax:put-syntax-table-property 0 '(1 . nil)))))
 
 (defun scala-syntax:propertize (start end)
-  "See syntax-propertize-function"
+  "See `syntax-propertize-function'"
   (scala-syntax:propertize-char-and-string-literals start end)
   (scala-syntax:propertize-shell-preamble start end)
   (scala-syntax:propertize-underscore-and-idrest start end)
@@ -647,12 +699,12 @@ symbol constituents (syntax 3)."
 ;;;;
 
 (defun scala-syntax:beginning-of-code-line ()
-  (interactive)
   "Move to the beginning of code on the line, or to the end of
 the line, if the line is empty. Return the new point.  Not to be
 called on a line whose start is inside a comment, i.e. a comment
 begins on the previous line and continues past the start of this
 line."
+  (interactive)
   ;; TODO: make it work even if the start IS inside a comment
   (beginning-of-line)
   (let ((eol (line-end-position))
@@ -668,9 +720,9 @@ line."
       (skip-syntax-forward " " eol)
       (point))))
 
+;; TODO Scala 3: nothing called stable
 (defun scala-syntax:looking-at-varid-p (&optional point)
-  "Return true if looking-at varid, and it is not the start of a
-stableId"
+  "Return true if `looking-at' varid, and it is not the start of a stableId"
   (save-excursion
     (when point (goto-char point))
     (scala-syntax:skip-forward-ignorable)
@@ -705,6 +757,7 @@ stableId"
     (and (looking-at scala-syntax:case-re)
          (goto-char (match-end 0))
          (scala-syntax:skip-forward-ignorable)
+         ;; TODO "not in an enum environment"
          (not (looking-at-p scala-syntax:class-or-object-re)))))
 
 (defun scala-syntax:looking-back-empty-line-p ()
@@ -742,8 +795,8 @@ and whitespace are skipped before matching."
     (looking-at re)))
 
 (defun scala-syntax:looking-back-token (re &optional max-chars)
-  "Return the start position of the token matched by re, if the
-current position is preceeded by it, or nil if not. All ignorable
+  "Return the start position of the token matched by RE, if the
+current position is preceded by it, or nil if not. All ignorable
 comments and whitespace are ignored, i.e. does not search past an
 empty line. Expects to be outside of comment. A limit for the
 search is calculated based on max-chars. The function won't look
@@ -784,6 +837,7 @@ one."
       (when (scala-syntax:looking-at "[[]")
         (forward-list)))))
 
+;; TODO if-then-else syntax
 (defun scala-syntax:looking-back-else-if-p ()
   ;; TODO: rewrite using (scala-syntax:if-skipped (scala:syntax:skip-backward-else-if))
   (save-excursion
@@ -791,17 +845,17 @@ one."
              (backward-list)
              (prog1 (scala-syntax:looking-back-token "if")
                (goto-char (match-beginning 0)))
+             (prog1 (scala-syntax:looking-back-token "then")
+               (goto-char (match-beginning 0)))
              (prog1 (scala-syntax:looking-back-token "else")
                (goto-char (match-beginning 0))))
         (point) nil)))
 
 (defun scala-syntax:newlines-disabled-p (&optional point)
-  "Return true if newlines are disabled at the current point (or
-point 'point') as specified by SLS chapter 1.2"
+  "Whether newlines are disabled at the current point (or point POINT)"
   ;; newlines are disabled if
   ;; - in '()' or '[]'
   ;; - between 'case' and '=>'
-  ;; - XML mode (not implemented here)
   (unless point (setq point (point)))
   (save-excursion
     (let* ((state (syntax-ppss point))
@@ -838,15 +892,15 @@ point 'point') as specified by SLS chapter 1.2"
                              (not (looking-at scala-syntax:class-or-object-re)))))))))))))
 
 (defun scala-syntax:forward-sexp ()
-  "Move forward one scala expression. It can be: parameter list (value or type),
-id, reserved symbol, keyword, block, or literal. Punctuation (.,;)
-and comments are skipped silently. Position is placed at the
-end of the skipped expression."
+  "Move forward one Scala expression.
+
+It can be: parameter list (value or type), id, reserved symbol, keyword, block,
+or literal. Punctuation (.,;) and comments are skipped silently. Position is
+placed at the end of the skipped expression."
   (interactive)
   (syntax-propertize (point-max))
-  ;; emacs knows how to properly skip: lists, varid, capitalid,
-  ;; strings, symbols, chars, quotedid. What we have to handle here is
-  ;; most of all ids made of op chars
+  ;; Emacs knows how to properly skip: lists, varid, strings, symbols, chars,
+  ;; quotedid. What we have to handle here is most of all ids made of op chars.
 
   ;; skip comments, whitespace and scala delimiter chars .,; so we
   ;; will be at the start of something interesting
@@ -859,11 +913,11 @@ end of the skipped expression."
     (goto-char (or (scan-sexps (point) 1) (buffer-end 1)))))
 
 (defun scala-syntax:forward-token ()
-  "Move forward one scala token, comment word or string word. It
-can be: start or end of list (value or type), id, reserved
-symbol, keyword, block, or literal. Punctuation (.,;), comment
-delimiters and string delimiters are skipped silently. Position
-is placed at the end of the skipped token."
+  "Move forward one Scala token, comment word or string word.
+
+It can be: start or end of list (value or type), id, reserved symbol, keyword,
+block, or literal. Punctuation (.,;), comment delimiters and string delimiters
+are skipped silently. Position is placed at the end of the skipped token."
   (interactive)
   (syntax-propertize (point-max))
   (skip-syntax-forward " >" (point-max))
@@ -900,7 +954,7 @@ is placed at the end of the skipped token."
   expression."
   (interactive)
   (syntax-propertize (point))
-  ;; for implementation comments, see scala-syntax:forward-sexp
+  ;; For implementation comments, see `scala-syntax:forward-sexp'
   (forward-comment (- (buffer-size)))
   (while (> 0 (+ (skip-syntax-backward " ")
                  (skip-chars-backward scala-syntax:delimiter-group))))
@@ -935,12 +989,12 @@ is returned, otherwise nil is returned"
     (when found (goto-char found))))
 
 (defun scala-syntax:list-p (&optional point)
-  "Returns the start of the list, if the current point (or point
-'point') is on the first line of a list element > 1, or nil if
+  "Returns the start of the list, if the current point (or POINT)
+is on the first line of a list element > 1, or nil if
 not. A list must be either enclosed in parentheses or start with
 'val', 'var' or 'import'."
   (save-excursion
-    ;; first check that the previous line ended with ','
+    ;; First check that the previous line ended with ','
     (when point (goto-char point))
     (scala-syntax:beginning-of-code-line)
     (when (and (scala-syntax:looking-back-token "," 1) (not (looking-at-p ")")))
@@ -958,26 +1012,47 @@ not. A list must be either enclosed in parentheses or start with
           (when (looking-at scala-syntax:list-keywords-re)
             (goto-char (match-end 0))))))))
 
-;; Functions to help with finding the beginning and end of scala definitions.
+;; Functions to help with finding the beginning and end of Scala definitions.
 
-(defconst scala-syntax:modifiers-re
-  (regexp-opt '("override" "abstract" "final" "sealed" "implicit" "lazy"
-                "private" "protected" "case") 'words))
+(defconst scala-syntax:local-modifiers
+  ;; TODO what about case, extension?
+  '("abstract" "final" "sealed" "open" "implicit" "lazy" "inline"))
+;; TODO Scala 3 BNF has:
+;;   (‘private’ | ‘protected’) [AccessQualifier]
+(defconst scala-syntax:access-modifiers
+  '("private" "protected"))
 
-(defconst scala-syntax:whitespace-delimeted-modifiers-re
+(defconst scala-syntax:modifiers-re
+  (regexp-opt (append '("override" "opaque")
+                      scala-syntax:local-modifiers
+                      scala-syntax:access-modifiers)
+              'words))
+
+(defconst scala-syntax:whitespace-delimited-modifiers-re
+  ;; NOTE this is sloppy because not all of these modifiers play together.
+  ;; Perhaps it's better that Emacs not get overly intelligent about this, lest
+  ;; its behavior be fragile in the face of partially incorrect code.
   (concat "\\(?:" scala-syntax:modifiers-re "\\(?: *\\)" "\\)*"))
 
+;; TODO work on the "Declarations and Definitions" section of the Scala 3 BNF
 (defconst scala-syntax:definition-words-re
-  (mapconcat 'regexp-quote '("class" "object" "trait" "val" "var" "def" "type") "\\|"))
+  ;; TODO is `type` the odd man out here? That is, can it introduce a block, or
+  ;; not?
+  ;; TODO Also, is it necessary to worry about which is introduced by which of
+  ;; these is introduced by `:`, `=`, `with`, &c?
+  ;; TODO what about `new`? Following by a trait or abstract class name and a
+  ;; colon, it can now introduce a block.
+  (mapconcat 'regexp-quote
+             '("class" "object" "trait" "val" "var" "def" "type" "enum" "given")
+             "\\|"))
 
 (defun scala-syntax:build-definition-re (words-re)
-  (concat " *"
-	  scala-syntax:whitespace-delimeted-modifiers-re
-	  words-re
-	  "\\(?: *\\)"
-	  "\\(?2:"
-	  scala-syntax:id-re
-	  "\\)"))
+  (concat scala-syntax:whitespace-delimited-modifiers-re
+          words-re
+          "\\(?: *\\)"
+          "\\(?2:"
+          scala-syntax:id-re ;; TODO should this be plain only?
+          "\\)"))
 
 (defconst scala-syntax:all-definition-re
   (scala-syntax:build-definition-re
@@ -989,26 +1064,36 @@ not. A list must be either enclosed in parentheses or start with
   (condition-case ex (backward-sexp) ('error (backward-char))))
 
 (defun scala-syntax:forward-sexp-or-next-line ()
+  ;; TODO this is a candidate for updating for whitespace
   (interactive)
   (cond ((looking-at "\n") (forward-line 1) (beginning-of-line))
 	(t (forward-sexp))))
 
+(defun scala-syntax:try-beginning-of-definition ()
+  (let ((max-indent (current-indentation))
+        (new-indent nil)
+        (pos nil))
+    (while (or (not new-indent) (and pos (>= new-indent max-indent)))
+      (scala-syntax:backward-sexp-forcing)
+      (setq pos (scala-syntax:movement-function-until-re
+                 scala-syntax:all-definition-re
+                 #'scala-syntax:backward-sexp-forcing))
+      (when pos (goto-char pos) (back-to-indentation))
+      (setq new-indent (current-indentation)))
+    pos))
+
 (defun scala-syntax:beginning-of-definition ()
-  "This function may not work properly with certain types of scala definitions.
+  "This function may not work properly with certain types of Scala definitions.
 For example, no care has been taken to support multiple assignments to vals such as
 
 val a, b = (1, 2)
 "
   (interactive)
-  (let ((found-position
-	 (save-excursion
-	   (scala-syntax:backward-sexp-forcing)
-	   (scala-syntax:movement-function-until-re scala-syntax:all-definition-re
-						    'scala-syntax:backward-sexp-forcing))))
-    (when found-position (progn (goto-char found-position) (back-to-indentation)))))
+  (let ((found-position (save-excursion (scala-syntax:try-beginning-of-definition))))
+    (when found-position (goto-char found-position) (back-to-indentation))))
 
 (defun scala-syntax:end-of-definition ()
-  "This function may not work properly with certain types of scala definitions.
+  "This function may not work properly with certain types of Scala definitions.
 For example, no care has been taken to support multiple assignments to vals such as
 
 val a, b = (1, 2)
@@ -1018,6 +1103,7 @@ val a, b = (1, 2)
   (scala-syntax:find-brace-equals-or-next)
   (scala-syntax:handle-brace-equals-or-next))
 
+;; TODO or : or... (indentation syntax)
 (defun scala-syntax:find-brace-equals-or-next ()
   (scala-syntax:go-to-pos
    (save-excursion
diff --git a/scala-mode.el b/scala-mode.el
index fe1bf58..0d961e9 100644
--- a/scala-mode.el
+++ b/scala-mode.el
@@ -88,15 +88,17 @@ If there is no plausible default, return nil."
 
 ;;;###autoload
 (defun scala-mode:goto-start-of-code ()
-  "Go to the start of the real code in the file: object, class or trait."
+  "Go to the start of the real code in the file.
+
+Object, class, trait, enum, def, val, or given."
   (interactive)
   (let* ((case-fold-search nil))
-    (search-forward-regexp "\\([[:space:]]+\\|^\\)\\(class\\|object\\|trait\\)" nil t)
+    (search-forward-regexp "\\([[:space:]]+\\|^\\)\\(class\\|enum\\|object\\|trait\\|def\\|val\\|given\\)" nil t)
     (move-beginning-of-line nil)))
 
 ;;;###autoload
 (define-derived-mode scala-mode prog-mode "Scala"
-  "Major mode for editing scala code.
+  "Major mode for editing Scala code.
 
 When started, runs `scala-mode-hook'.
 
diff --git a/test/scala-mode-test.el b/test/scala-mode-test.el
index 8a7a195..6d2b63a 100644
--- a/test/scala-mode-test.el
+++ b/test/scala-mode-test.el
@@ -176,3 +176,29 @@ comment. A concrete example may be viewed at https://github.com/scala/scala/blob
    "/* &*/"
    "110111"
    "DDDOOO"))
+
+(ert-deftest scala-indent:scala-indent:use-cycle-indent-test-1 ()
+  "Custom \"scala-indent:use-cycle-indent\" should be nil by default."
+  
+  (should-not scala-indent:use-cycle-indent ))
+
+(ert-deftest scala-indent:scala-indent:use-cycle-indent-test-2 ()
+  "Custom \"scala-indent:use-cycle-indent\" must be a boolean."
+  
+  :expected-result :failed
+  (custom-set-variables '(scala-indent:use-cycle-indent "gobbledygook"))
+  (should (= 'scala-indent:use-cycle-indent "gobbledygook")))
+
+(ert-deftest scala-indent:scala-indent:use-cycle-indent-test-3 ()
+  "Custom \"scala-indent:use-cycle-indent\" should be settable to a boolean value."
+  
+  (custom-set-variables '(scala-indent:use-cycle-indent t))
+  (should 'scala-indent:use-cycle-indent )
+  (custom-set-variables '(scala-indent:use-cycle-indent nil)))
+
+(ert-deftest scala-indent:scala-indent:cycle-indent-stack-test-1 ()
+  "\"scala-indent:cycle-indent-stack\" should be 0 current-indentation after one call."
+  (custom-set-variables '(scala-indent:use-cycle-indent t))
+  (call-interactively (scala-indent:indent-line))
+  (should (buffer-local-value 'scala-indent:cycle-indent-stack))
+  (custom-set-variables '(scala-indent:use-cycle-indent nil)))