From ba2e0489e679521db13bb36ba60a9623ee54706d Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Fri, 27 Nov 2020 15:07:38 +0900 Subject: [PATCH 1/3] Add initial implementation of docstring formatter --- src/boot/boot.janet | 264 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 263 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 8812b074..12efe14f 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1658,6 +1658,268 @@ [&opt env local] (env-walk keyword? env local)) +(defn docstring-format + "Reformat a docstring." + [str &opt width indent] + (default indent 4) + (def max-width (- (or width (dyn :doc-width 80)) 8)) + (def len (length str)) + (def res @"") + (var pos 0) + (def line @"") + (var line-width 0) + (def levels @[0]) + (var level 0) + (var c nil) + + (def base-indent + # Is there a better way? + (do + (var min-indent 0) + (var curr-indent 0) + (var start-of-line false) + (set c (get str pos)) + (while (not= nil c) + (case c + 10 (do + (set start-of-line true) + (set curr-indent 0)) + 32 (when start-of-line + (++ curr-indent)) + (when start-of-line + (set start-of-line false) + (when (or (= 0 min-indent) + (< curr-indent min-indent)) + (set min-indent curr-indent)))) + (set c (get str (++ pos)))) + min-indent)) + + (set pos 0) + + (defn skip-base-indent [] + (var pos* pos) + (set c (get str pos*)) + (while (and (< (- pos* pos) base-indent) + (= 32 c)) + (set c (get str (++ pos*)))) + (set pos pos*)) + + (defn update-level [] + (var pos* pos) + (set c (get str pos*)) + (while (and (not= nil c) + (= 32 c)) + (set c (get str (++ pos*)))) + (set level (- pos* pos))) + + (defn reset-level [] + (while (< level (array/peek levels)) + (array/pop levels)) + (+= pos level)) + + (defn start-nl? [] + (= 10 (get str pos))) + + (defn start-fcb? [] + (and (or (= 0 level) + (= (array/peek levels) level)) + (= 96 (get str (+ level pos))) + (= 96 (get str (+ level pos 1))) + (= 96 (get str (+ level pos 2))))) + + (defn end-fcb? [] + (and (or (= 0 level) + (= (array/peek levels) level)) + (= 96 (get str (+ level pos))) + (= 96 (get str (+ level pos 1))) + (= 96 (get str (+ level pos 2))) + (= 10 (get str (+ level pos 3))))) + + (defn start-icb? [] + (and (not= level (array/peek levels)) + (or (= 4 level) + (= 4 (- level (array/peek levels)))))) + + (defn start-ul? [] + (var pos* pos) + (var c* (get str pos*)) + (while (and (not= nil c*) + (= 32 c*)) + (set c* (get str (++ pos*)))) + (and (or (= 42 c*) + (= 43 c*) + (= 45 c*)) + (= 32 (get str (+ pos* 1))))) + + (defn start-ol? [] + (var pos* pos) + (var c* (get str pos*)) + (while (and (not= nil c*) + (= 32 c*)) + (set c* (get str (++ pos*)))) + (while (and (not= nil c*) + (<= 48 c*) + (>= 57 c*)) + (set c* (get str (++ pos*)))) + (set c* (get str (-- pos*))) + (and (<= 48 c*) + (>= 57 c*) + (= 46 (get str (+ pos* 1))) + (= 32 (get str (+ pos* 2))))) + + (defn push-line [] + (buffer/push-string res (buffer/new-filled indent 32)) + (set c (get str pos)) + (while (not= 10 c) + (buffer/push-byte res c) + (set c (get str (++ pos)))) + (buffer/push-byte res c) + (++ pos)) + + (defn push-bullet [] + (var pos* pos) + (set c (get str pos*)) + # Add leading space + (while (and (not= nil c) (= 32 c)) + (buffer/push-byte line c) + (set c (get str (++ pos*)))) + # Add bullet + (while (and (not= nil c) (not= 32 c)) + (buffer/push-byte line c) + (set c (get str (++ pos*)))) + # Add item indentation + (while (and (not= nil c) (= 32 c)) + (buffer/push-byte line c) + (set c (get str (++ pos*)))) + # Record indentation if necessary + (def item-level (+ level (- pos* pos))) + (when (not= item-level (array/peek levels)) + (array/push levels item-level)) + (set line-width item-level) + # Update position + (set pos pos*)) + + (defn push-word [hang-indent] + (def word @"") + (var word-len 0) + # Build a word + (while (and (not= nil c) (not= 10 c) (not= 32 c)) + (buffer/push-byte word c) + (++ word-len) + (set c (get str (++ pos)))) + # Start new line if necessary + (when (> (+ line-width word-len) max-width) + # Push existing line + (buffer/push-byte line 10) + (buffer/push-string res line) + (buffer/clear line) + # Indent new line + (buffer/push-string line (buffer/new-filled hang-indent 32)) + (set line-width hang-indent)) + # Add single space if not beginning of line + (when (not= line-width hang-indent) + (buffer/push-byte line 32) + (++ line-width)) + # Push word onto line + (buffer/push-string line word) + (set line-width (+ line-width word-len))) + + (defn push-nl [] + (when (< pos len) + (buffer/push-byte res 10) + (++ pos))) + + (defn push-list [] + (reset-level) + # Set up the indentation + (def list-indent (+ indent (array/peek levels))) + # Indent first line + (buffer/push-string line (buffer/new-filled list-indent 32)) + (set line-width list-indent) + # Add bullet + (push-bullet) + # Add words + (set c (get str pos)) + (while (and (not= nil c) + (not= 10 c) + (not (or (start-ul?) + (start-ol?)))) + # Skip spaces + (while (= 32 c) + (set c (get str (++ pos)))) + # Add word + (push-word (+ list-indent (array/peek levels))) + (set c (get str (++ pos)))) + # Add final line + (buffer/push-string res line) + (buffer/clear line) + # Move position back for newline + (-- pos) + (push-nl)) + + (defn push-fcb [] + (push-line) + (skip-base-indent) + (while (not (end-fcb?)) + (push-line) + (skip-base-indent)) + (push-line)) + + (defn push-icb [] + (push-line) + (skip-base-indent) + (while (not (start-nl?)) + (push-line) + (skip-base-indent)) + (push-nl)) + + (defn push-p [] + (reset-level) + # Set up the indentation + (def para-indent (+ indent (array/peek levels))) + # Indent first line + (buffer/push-string line (buffer/new-filled para-indent 32)) + (set line-width para-indent) + # Add words + (set c (get str pos)) + (while (and (not= nil c) + (not= 10 c)) + # Skip spaces + (while (= 32 c) + (set c (get str (++ pos)))) + # Add word + (push-word para-indent) + (set c (get str (++ pos)))) + # Add final line + (buffer/push-string res line) + (buffer/clear line) + # Move position back for newline + (-- pos) + (push-nl) + (push-nl)) + + (while (< pos len) + (skip-base-indent) + (update-level) + (cond + (start-nl?) + (push-nl) + + (start-ul?) + (push-list) + + (start-ol?) + (push-list) + + (start-fcb?) + (push-fcb) + + (start-icb?) + (push-icb) + + (push-p))) + res) + (defn doc-format "Reformat text to wrap at a given line." [text &opt width] @@ -1735,7 +1997,7 @@ (if-let [[path line col] sm] (string " " path " on line " line ", column " col "\n") "") (if (or d sm) "\n" "") - (if d (doc-format d) " no documentation found.") + (if d (docstring-format d) " no documentation found.") "\n\n")))) # else From 02224d5aa9c28e48b7a24576996de78083ae150c Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Fri, 27 Nov 2020 18:28:58 +0900 Subject: [PATCH 2/3] Fix bugs in parsing logic --- src/boot/boot.janet | 85 +++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 12efe14f..83de0de7 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1669,7 +1669,7 @@ (def line @"") (var line-width 0) (def levels @[0]) - (var level 0) + (var leading 0) (var c nil) (def base-indent @@ -1704,41 +1704,38 @@ (set c (get str (++ pos*)))) (set pos pos*)) - (defn update-level [] + (defn skip-line-indent [] (var pos* pos) (set c (get str pos*)) (while (and (not= nil c) + (not= 10 c) (= 32 c)) (set c (get str (++ pos*)))) - (set level (- pos* pos))) + (set leading (- pos* pos)) + (set pos pos*)) - (defn reset-level [] - (while (< level (array/peek levels)) - (array/pop levels)) - (+= pos level)) + (defn update-levels [] + (while (< leading (array/peek levels)) + (array/pop levels))) (defn start-nl? [] (= 10 (get str pos))) (defn start-fcb? [] - (and (or (= 0 level) - (= (array/peek levels) level)) - (= 96 (get str (+ level pos))) - (= 96 (get str (+ level pos 1))) - (= 96 (get str (+ level pos 2))))) + (and (= 96 (get str (+ pos))) + (= 96 (get str (+ pos 1))) + (= 96 (get str (+ pos 2))))) (defn end-fcb? [] - (and (or (= 0 level) - (= (array/peek levels) level)) - (= 96 (get str (+ level pos))) - (= 96 (get str (+ level pos 1))) - (= 96 (get str (+ level pos 2))) - (= 10 (get str (+ level pos 3))))) + (and (= 96 (get str (+ pos))) + (= 96 (get str (+ pos 1))) + (= 96 (get str (+ pos 2))) + (= 10 (get str (+ pos 3))))) (defn start-icb? [] - (and (not= level (array/peek levels)) - (or (= 4 level) - (= 4 (- level (array/peek levels)))))) + (and (not= leading (array/peek levels)) + (or (= 4 leading) + (= 4 (- leading (array/peek levels)))))) (defn start-ul? [] (var pos* pos) @@ -1778,11 +1775,8 @@ (defn push-bullet [] (var pos* pos) + (buffer/push-string line (buffer/new-filled leading 32)) (set c (get str pos*)) - # Add leading space - (while (and (not= nil c) (= 32 c)) - (buffer/push-byte line c) - (set c (get str (++ pos*)))) # Add bullet (while (and (not= nil c) (not= 32 c)) (buffer/push-byte line c) @@ -1792,10 +1786,11 @@ (buffer/push-byte line c) (set c (get str (++ pos*)))) # Record indentation if necessary - (def item-level (+ level (- pos* pos))) - (when (not= item-level (array/peek levels)) - (array/push levels item-level)) - (set line-width item-level) + (def item-indent (+ leading (- pos* pos))) + (when (not= item-indent (array/peek levels)) + (array/push levels item-indent)) + # Update line width + (+= line-width item-indent) # Update position (set pos pos*)) @@ -1803,7 +1798,9 @@ (def word @"") (var word-len 0) # Build a word - (while (and (not= nil c) (not= 10 c) (not= 32 c)) + (while (and (not= nil c) + (not= 10 c) + (not= 32 c)) (buffer/push-byte word c) (++ word-len) (set c (get str (++ pos)))) @@ -1830,26 +1827,28 @@ (++ pos))) (defn push-list [] - (reset-level) - # Set up the indentation - (def list-indent (+ indent (array/peek levels))) + (update-levels) # Indent first line - (buffer/push-string line (buffer/new-filled list-indent 32)) - (set line-width list-indent) + (buffer/push-string line (buffer/new-filled indent 32)) + (set line-width indent) # Add bullet (push-bullet) # Add words (set c (get str pos)) (while (and (not= nil c) - (not= 10 c) - (not (or (start-ul?) - (start-ol?)))) + (not= 10 c)) # Skip spaces (while (= 32 c) (set c (get str (++ pos)))) # Add word - (push-word (+ list-indent (array/peek levels))) - (set c (get str (++ pos)))) + (push-word (+ indent (array/peek levels))) + (def old-c c) + (set c (get str (++ pos))) + # Check if next line is a new item + (when (and (= 10 old-c) + (or (start-ul?) + (start-ol?))) + (set c (get str (-- pos))))) # Add final line (buffer/push-string res line) (buffer/clear line) @@ -1858,6 +1857,7 @@ (push-nl)) (defn push-fcb [] + (update-levels) (push-line) (skip-base-indent) (while (not (end-fcb?)) @@ -1866,6 +1866,7 @@ (push-line)) (defn push-icb [] + (buffer/push-string res (buffer/new-filled leading 32)) (push-line) (skip-base-indent) (while (not (start-nl?)) @@ -1874,7 +1875,7 @@ (push-nl)) (defn push-p [] - (reset-level) + (update-levels) # Set up the indentation (def para-indent (+ indent (array/peek levels))) # Indent first line @@ -1900,7 +1901,7 @@ (while (< pos len) (skip-base-indent) - (update-level) + (skip-line-indent) (cond (start-nl?) (push-nl) From 6f3eff32584e891b4badbd46445a7256d11514d4 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Fri, 27 Nov 2020 18:29:41 +0900 Subject: [PATCH 3/3] Add example docstring --- src/boot/boot.janet | 79 ++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 83de0de7..7ef0ea02 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -529,39 +529,58 @@ (each-template x ds :each body)) (defmacro loop - "A general purpose loop macro. This macro is similar to the Common Lisp + ``A general purpose loop macro. This macro is similar to the Common Lisp loop macro, although intentionally much smaller in scope. The head of the loop should be a tuple that contains a sequence of either bindings or conditionals. A binding is a sequence of three values - that define something to loop over. They are formatted like:\n\n - \tbinding :verb object/expression\n\n - Where binding is a binding as passed to def, :verb is one of a set of keywords, - and object is any expression. The available verbs are:\n\n - \t:iterate - repeatedly evaluate and bind to the expression while it is truthy.\n - \t:range - loop over a range. The object should be a two-element tuple with a start - and end value, and an optional positive step. The range is half open, [start, end).\n - \t:range-to - same as :range, but the range is inclusive [start, end].\n - \t:down - loop over a range, stepping downwards. The object should be a two-element tuple - with a start and (exclusive) end value, and an optional (positive!) step size.\n - \t:down-to - same :as down, but the range is inclusive [start, end].\n - \t:keys - iterate over the keys in a data structure.\n - \t:pairs - iterate over the key-value pairs as tuples in a data structure.\n - \t:in - iterate over the values in a data structure.\n - \t:generate - iterate over values yielded from a fiber. Can be paired with the generator - function for the producer/consumer pattern.\n\n - loop also accepts conditionals to refine the looping further. Conditionals are of - the form:\n\n - \t:modifier argument\n\n - where :modifier is one of a set of keywords, and argument is keyword-dependent. - :modifier can be one of:\n\n - \t:while expression - breaks from the loop if expression is falsey.\n - \t:until expression - breaks from the loop if expression is truthy.\n - \t:let bindings - defines bindings inside the loop as passed to the let macro.\n - \t:before form - evaluates a form for a side effect before the next inner loop.\n - \t:after form - same as :before, but the side effect happens after the next inner loop.\n - \t:repeat n - repeats the next inner loop n times.\n - \t:when condition - only evaluates the loop body when condition is true.\n\n - The loop macro always evaluates to nil." + that define something to loop over. They are formatted like: + + binding :verb object/expression + + Where `binding` is a binding as passed to def, `:verb` is one of a set of + keywords, and `object` is any expression. The available verbs are: + + * :iterate -- repeatedly evaluate and bind to the expression while it is + truthy. + + * :range -- loop over a range. The object should be a two-element tuple with + a start and end value, and an optional positive step. The range is half + open, [start, end). + + * :range-to -- same as :range, but the range is inclusive [start, end]. + + * :down -- loop over a range, stepping downwards. The object should be a + two-element tuple with a start and (exclusive) end value, and an optional + (positive!) step size. + + * :down-to -- same :as down, but the range is inclusive [start, end]. + + * :keys -- terate over the keys in a data structure. + + * :pairs -- iterate over the key-value pairs as tuples in a data structure. + + * :in -- iterate over the values in a data structure. + + * :generate -- iterate over values yielded from a fiber. Can be paired with + the generator function for the producer/consumer pattern. + + `loop` also accepts conditionals to refine the looping further. Conditionals are of + the form: + + :modifier argument + + where `:modifier` is one of a set of keywords, and `argument` is keyword-dependent. + `:modifier` can be one of: + + * `:while expression` - breaks from the loop if `expression` is falsey. + * `:until expression` - breaks from the loop if `expression` is truthy. + * `:let bindings` - defines bindings inside the loop as passed to the `let` macro. + * `:before form` - evaluates a form for a side effect before the next inner loop. + * `:after form` - same as `:before`, but the side effect happens after the next inner loop. + * `:repeat n` - repeats the next inner loop `n` times. + * `:when condition` - only evaluates the loop body when condition is true. + + The `loop` macro always evaluates to nil.`` [head & body] (loop1 body head 0))