std.block

Code representation, traversal, and manipulation.

std.block turns Clojure source into a tree of typed blocks that preserve whitespace, comments, reader macros, and exact token strings. It is the foundation for parsing, layout, healing, templating, and source-level refactoring in foundation-base.

1    The Block Model

1.1    Why std.block exists

Most Clojure readers discard formatting when they turn source into data. read-string gives you values, but it loses comments, extra spaces, and reader macros. std.block keeps those details as first-class nodes so code can be analyzed, transformed, and printed back out without destroying the programmer's formatting.

read-string loses whitespace; std.block keeps it

(-> (parse/parse-string "[1   2]")
    str)
=> "[1   2]"

comments survive a parse/string round trip

(-> (parse/parse-root ";; hello\n[1 2]")
    str)
=> ";; hello\n[1 2]"

1.2    The five block types

Every block has a :type. The five types are:

  • :token — literals, symbols, keywords, strings, numbers
  • :container — lists, vectors, maps, sets, and cons forms
  • :void — whitespace, newlines, tabs, EOF
  • :comment; line comments
  • :modifier — uneval (#_) and cursor (#|) markers

a keyword is a token

(-> (parse/parse-string ":hello")
    base/block-type)
=> :token

a vector is a collection container

(-> (parse/parse-string "[1 2]")
    base/block-type)
=> :collection

a line comment is its own block type

(-> (parse/parse-string ";comment")
    base/block-type)
=> :comment

1.3    Block tags refine the type

A :tag narrows the type. Tokens can be :long, :double, :boolean, :string, :keyword, :symbol, and so on. Containers carry tags like :list, :vector, :map, :set, :quote, :deref, :meta, :fn, etc.

token tags distinguish values

(-> (parse/parse-string "true")
    base/block-info)
=> (contains {:type :token
              :tag  :boolean
              :string "true"})

container tags distinguish collection kinds

(-> (parse/parse-string "#{1}")
    base/block-info)
=> (contains {:type :collection
              :tag  :set})

1.4    Block info summarises a node

base/block-info returns the type, tag, string, width, and height of any block. It is the quickest way to inspect a node without reaching into implementation details.

block-info on a void block

(-> (parse/parse-string "\t")
    base/block-info)
=> (contains {:type :void
              :tag  :linetab
              :string "\t"})

block-info on a container includes dimensions

(-> (parse/parse-string "[1 2 3]")
    base/block-info)
=> (contains {:type :collection
              :tag  :vector
              :width 7
              :height 0})

1.5    Tokens carry values

Expression blocks (tokens and containers) can produce a Clojure value with base/block-value. Tokens that are not expressions, such as whitespace, have no value.

numeric token value

(-> (parse/parse-string "3/5")
    base/block-value)
=> 3/5

symbol token value

(-> (parse/parse-string "hello/world")
    base/block-value)
=> 'hello/world

string token value

(-> (parse/parse-string "\"hello\"")
    base/block-value)
=> "hello"

1.6    Containers have children

Containers store a vector of child blocks. The child list preserves every space, newline, and comment that appeared between the delimiters. Use base/block-children to access them and base/replace-children to create a new container.

children preserve spaces

(->> (parse/parse-string "[1   2]")
     base/block-children
     (map str))
=> '["1" "␣" "␣" "␣" "2"]

replace-children rebuilds a container

(-> (parse/parse-string "[1 2]")
    (base/replace-children [(construct/token 3)
                            (construct/space)
                            (construct/token 4)])
    str)
=> "[3 4]"

1.7    Void blocks model whitespace

:void blocks represent spaces, tabs, newlines, carriage returns, form feeds, and EOF. Their string representation is the literal character, but str on a space void returns the visible placeholder to make whitespace observable in tests.

visible space representation

(str (construct/space))
=> "␣"

newline representation

(str (construct/newline))
=> "\\n"

tab representation

(str (construct/tab))
=> "\\t"

1.8    Comments are first-class blocks

A semicolon comment becomes a single :comment block. Because it is a node in the tree, comments can be preserved, moved, or deleted during transformations.

construct a comment block

(str (construct/comment ";note"))
=> ";note"

comments are children of containers

(->> (parse/parse-string "[1 ;note\n 2]")
     base/block-children
     (map base/block-type))
=> '(:token :void :comment :void :void :token)

1.9    Modifiers attach behaviour

:modifier blocks currently implement #_ (uneval) and #| (cursor). A modifier can modify an accumulator when evaluated. #_ suppresses the next expression during value extraction.

uneval is a modifier

(-> (parse/parse-string "#_ignored")
    base/block-type)
=> :modifier

uneval suppresses the next expression

(-> (parse/parse-string "[1 #_2 3]")
    base/block-value)
=> [1 3]

1.10    Dimensions: width, height, length

Every block knows its on-screen width, its height in lines, and the length of its raw string. These dimensions power layout and navigation.

single-line block dimensions

(-> (parse/parse-string "hello")
    ((juxt base/block-width base/block-height base/block-length)))
=> [5 0 5]

multi-line string dimensions

(-> (parse/parse-string "\"a\nb\"")
    ((juxt base/block-width base/block-height)))
=> [2 1]

2    Parsing Source Code

2.1    parse-string reads one form

parse/parse-string reads a single top-level form from a string. It is the simplest entry point for converting source text into a block tree.

parse a vector

(-> (parse/parse-string "[1 2 3]")
    str)
=> "[1 2 3]"

parse a nested map

(-> (parse/parse-string "{:a [1 2]}")
    str)
=> "{:a [1 2]}"

2.2    parse-root reads many forms

parse/parse-root reads every form until EOF and wraps them in a :root container. Use it when you want to represent an entire file or snippet.

parse-root keeps multiple forms

(-> (parse/parse-root "a b c")
    str)
=> "a b c"

parse-root preserves inter-form whitespace

(-> (parse/parse-root "a\n\n  b")
    str)
=> "a\n\n  b"

2.3    parse-first returns the first expression

parse/parse-first skips leading whitespace and returns the first code expression. It is useful when you only care about the initial form.

parse-first ignores leading whitespace

(-> (parse/parse-first "  (+ 1 2)")
    str)
=> "(+ 1 2)"

parse-first returns a block info map

(-> (parse/parse-first "1 2 3")
    base/block-info)
=> (contains {:type :token
              :tag  :long
              :string "1"})

2.4    Reading collections

The parser recognises the four Clojure collection types: lists (), vectors [], maps {}, and sets #{}. Each becomes a :container with the matching tag.

list value

(-> (parse/parse-string "(1 2 3)")
    base/block-value)
=> '(1 2 3)

vector value

(-> (parse/parse-string "[1 2 3]")
    base/block-value)
=> [1 2 3]

map value

(-> (parse/parse-string "{:a 1 :b 2}")
    base/block-value)
=> {:a 1 :b 2}

set value

(-> (parse/parse-string "#{1 2 3}")
    base/block-value)
=> #{1 2 3}

2.5    Reading cons forms

Reader macros that prepend a single expression (', @, `` ``, ~, ~@) and metadata ( and #) become :container blocks tagged as :quote, :deref, :syntax, :unquote, :unquote-splice, or :meta`.

quote becomes a cons container

(-> (parse/parse-string "'x")
    base/block-info)
=> (contains {:type :collection
              :tag  :quote
              :string "'x"})

deref value

(-> (parse/parse-string "@atom")
    base/block-value)
=> '(deref atom)

metadata cons

(-> (parse/parse-string "^:tag [1]")
    base/block-value)
=> [1]

2.6    Reading hash dispatch

The # reader dispatch recognises sets, anonymous functions, regexes, vars, keywords, metadata, eval, conditional read, uneval, and cursor markers.

anonymous function

(-> (parse/parse-string "#(+ 1 %)")
    base/block-value
    first)
=> 'fn*

regex token

(-> (parse/parse-string "#\"a+b\"")
    base/block-value)
=> #(instance? java.util.regex.Pattern %)

var quote

(-> (parse/parse-string "#'inc")
    base/block-value)
=> '(var inc)

hash keyword

(-> (parse/parse-string "#:person{:name \"A\"}")
    base/block-value)
=> #:person{:name "A"}

2.7    Preserving whitespace

Spaces, tabs, newlines, commas, and EOF are all modelled as void blocks. This is why round-tripping source through parse-root followed by str gives back the exact original text.

round-trip keeps extra spaces

(-> (parse/parse-root "( 1   2 )")
    str)
=> "( 1   2 )"

commas become void blocks

(-> (parse/parse-string "[1,2]")
    str)
=> "[1,2]"

2.8    Preserving comments

Comments are parsed as :comment blocks and kept inside containers. They participate in the child list just like whitespace.

comment inside a list

(-> (parse/parse-string "(1 ;two\n 3)")
    str)
=> "(1 ;two\n 3)"

comment at top level

(-> (parse/parse-root ";; header\n(+ 1 2)")
    str)
=> ";; header\n(+ 1 2)"

2.9    Reader conditionals

#? and #?@ are parsed as :select and :select-splice containers. Their values are represented as (? ...) and (?-splicing ...) forms.

reader conditional

(-> (parse/parse-string "#?(:cljs a :clj b)")
    base/block-value)
=> '(? {:cljs a :clj b})

reader conditional splice

(-> (parse/parse-string "#?@(:cljs [a b])")
    base/block-value)
=> '(?-splicing {:cljs [a b]})

2.10    Parsing errors

Unmatched delimiters raise reader errors with the offending character. EOF inside a string or collection is also detected.

unmatched delimiter throws

(parse/parse-string "(]")
=> (throws)

unexpected EOF throws

(parse/parse-string "(1 2")
=> (throws)

3    Constructing and Modifying Blocks

3.1    The block helper

construct/block is the universal constructor. Pass it a primitive, a collection, or an existing block and it returns the appropriate block type.

block from a number

(-> (construct/block 42)
    base/block-info)
=> (contains {:type :token
              :tag  :long
              :string "42"})

block from a vector

(-> (construct/block [1 2 3])
    str)
=> "[1 2 3]"

block from a map

(-> (construct/block {:a 1})
    str)
=> "{:a 1}"

block passes an existing block through

(let [b (construct/block 1)]
  (= b (construct/block b)))
=> true

3.2    Constructing tokens

construct/token builds a token from any Clojure value. construct/token-from-string builds a token from source text and infers the value. construct/string-token is used internally for strings.

token from a symbol

(str (construct/token 'foo))
=> "foo"

token from a ratio

(str (construct/token 3/4))
=> "3/4"

token from a string

(str (construct/token "hello"))
=> "\"hello\""

token from string source

(-> (construct/token-from-string "1.5")
    base/block-value)
=> 1.5

3.3    Constructing void blocks

Use construct/space, construct/newline, construct/tab, and the general construct/void to create whitespace. Bulk constructors like construct/spaces and construct/newlines repeat a void block.

multiple spaces

(apply str (construct/spaces 3))
=> "␣␣␣"

multiple newlines

(apply str (construct/newlines 2))
=> "\\n\\n"

void from a character

(str (construct/void \newline))
=> "\\n"

3.4    Constructing comments

construct/comment creates a comment block from a string. The string must begin with ;.

valid comment

(str (construct/comment "; TODO: fix"))
=> "; TODO: fix"

invalid comment throws

(construct/comment "not a comment")
=> (throws)

3.5    Constructing containers

construct/container builds a container from a tag and a vector of children. The tag must be one of the known container tags such as :list, :vector, :map, :set, :quote, :meta, etc.

empty list container

(str (construct/container :list []))
=> "()"

list with children

(-> (construct/container :list [(construct/token '+)
                                (construct/space)
                                (construct/token 1)])
    str)
=> "(+ 1)"

set container

(str (construct/container :set [(construct/token 1)]))
=> "#{1}"

3.6    Adding children

construct/add-child appends a block to a container's children. It does not insert spacing automatically, so you usually interleave spaces or newlines yourself.

add-child without spacing

(-> (construct/block [])
    (construct/add-child 1)
    (construct/add-child 2)
    str)
=> "[12]"

add-child with explicit spacing

(-> (construct/block [])
    (construct/add-child 1)
    (construct/add-child (construct/space))
    (construct/add-child 2)
    str)
=> "[1 2]"

3.7    Replacing children

base/replace-children swaps the entire child vector. This is the low-level primitive used by add-child and by navigational updates.

replace with formatted children

(-> (construct/block [1 2])
    (base/replace-children [(construct/token 3)
                            (construct/space)
                            (construct/token 4)])
    str)
=> "[3 4]"

replace with nested container

(-> (construct/block [1])
    (base/replace-children [(construct/block [2 3])])
    str)
=> "[[2 3]]"

3.8    Building root blocks

construct/root wraps a sequence of children in a :root container. Use it when you need a top-level node that can hold multiple forms.

root with two forms

(-> (construct/root [(construct/token 'a)
                     (construct/space)
                     (construct/token 'b)])
    str)
=> "a b"

root round-trip

(-> (parse/parse-root "a b")
    str)
=> "a b"

3.9    Reading contents back

construct/contents converts a container's children back into Clojure forms. It is useful when you want the data inside a container without the container itself.

contents of a vector

(construct/contents (construct/block [1 2 3]))
=> '[1 ␣ 2 ␣ 3]

contents of a list

(construct/contents (construct/block '(+ 1 2)))
=> '(+ ␣ 1 ␣ 2)

3.10    Indentation helpers

construct/indent-body re-indents a multi-line container's children by a given offset. construct/max-width and construct/line-width measure block dimensions for layout tasks.

indent-body adds leading spaces

(-> (construct/block [(construct/token 'x)
                      (construct/newline)
                      (construct/token 'y)])
    (construct/indent-body 2)
    str)
=> "[x\n  y]"

max-width of a single-line block

(construct/max-width (construct/block [1 2 3]))
=> 7

4    Navigating and Transforming Blocks

4.1    Creating navigators

std.block.navigate wraps a block tree in a zipper. nav/navigator turns a block into a navigator; nav/parse-string, nav/parse-root, and nav/parse-first parse source and return a navigator in one step.

navigator from a block

(-> (nav/navigator (construct/block [1 2 3]))
    nav/value)
=> [1 2 3]

parse-string returns a navigator

(-> (nav/parse-string "[1 2]")
    nav/value)
=> [1 2]

parse-root returns a navigator

(-> (nav/parse-root "a b")
    nav/root-string)
=> "a b"

4.2    Up, down, left, and right

Navigators move through the tree. down enters the current container, up exits to the parent, and left/right move between expressions at the same level. The starred variants left* and right* include whitespace blocks.

down enters a container

(-> (nav/parse-string "(+ 1 2)")
    nav/down
    nav/value)
=> '+

right moves to the next expression

(-> (nav/parse-string "(+ 1 2)")
    nav/down
    nav/right
    nav/value)
=> 1

up returns to the parent

(-> (nav/parse-string "(+ 1 2)")
    nav/down
    nav/right
    nav/up
    nav/value)
=> '(+ 1 2)

4.3    Expression-aware navigation

left and right skip whitespace and stop at the next expression. left-most and right-most jump to the ends of the current level.

right skips whitespace

(-> (nav/parse-string "(+ 1 2)")
    nav/down
    nav/right
    nav/right
    nav/value)
=> 2

right-most reaches the last expression

(-> (nav/parse-string "(+ 1 2 3)")
    nav/down
    nav/right-most
    nav/value)
=> 3

4.4    Token navigation

left-token, right-token, prev-token, and next-token move specifically between :token blocks. This is handy when you are searching for a keyword or symbol.

down reaches the first keyword in a map

(-> (nav/parse-string "{:a 1 :b 2}")
    nav/down
    nav/value)
=> :a

right-token skips to the next token

(-> (nav/parse-string "{:a 1 :b 2}")
    nav/down
    nav/right-token
    nav/value)
=> 1

right-most-token stops at the last token

(-> (nav/parse-string "{:a 1 :b 2}")
    nav/down
    nav/right-most-token
    nav/value)
=> 2

4.5    Finding by predicate

find-next, find-prev, find-next-token, and find-prev-token search the zipper for blocks matching a predicate or token value. They are the backbone of refactoring recipes.

find-next by value

(-> (nav/parse-root "a b c")
    (nav/find-next-token 'b)
    nav/value)
=> 'b

next moves to the next expression

(-> (nav/parse-string "[1 :key 3]")
    nav/down
    nav/next
    nav/value)
=> :key

4.6    Inserting elements

insert-token-to-right, insert-token-to-left, and insert-token add new expressions with appropriate spacing. insert-empty is used when the container has no expressions.

insert token to the right

(-> (nav/parse-string "[1]")
    nav/down
    (nav/insert-token-to-right 2)
    nav/root-string)
=> "[1 2]"

insert token to the left

(-> (nav/parse-string "[2]")
    nav/down
    (nav/insert-token-to-left 1)
    nav/root-string)
=> "[1 2]"

insert multiple tokens

(-> (nav/parse-string "[1]")
    nav/down
    (nav/insert-all [2 3])
    nav/root-string)
=> "[1 2 3]"

4.7    Deleting elements

delete removes the current expression, delete-left removes the expression to the left, and delete-right removes the expression to the right. backspace combines tightening and deletion.

delete current expression

(-> (nav/parse-string "[1 2 3]")
    nav/down
    nav/right
    nav/delete
    nav/root-string)
=> "[1 3]"

delete left expression

(-> (nav/parse-string "[1 2 3]")
    nav/down
    nav/right
    nav/right
    nav/delete-left
    nav/root-string)
=> "[1 3]"

4.8    Replacing and splicing

replace swaps the current expression for a single block. replace-splice swaps it for a sequence of blocks, expanding them in place. swap applies a function to the current value and replaces it.

replace a symbol

(-> (nav/parse-string "[inc 1]")
    nav/down
    (nav/replace (construct/token 'dec))
    nav/root-string)
=> "[dec 1]"

replace-splice expands a list

(-> (nav/parse-string "[1 placeholder 2]")
    nav/down
    nav/right
    (nav/replace-splice [(construct/token 'a)
                         (construct/space)
                         (construct/token 'b)])
    nav/root-string)
=> "[1 a   b 2]"

swap with inc

(-> (nav/parse-string "[1]")
    nav/down
    (nav/swap inc)
    nav/root-string)
=> "[2]"

4.9    Tightening whitespace

tighten-left, tighten-right, and tighten normalise spacing around the cursor. They collapse multiple spaces to a single space and ensure comments are followed by newlines.

tighten removes extra spaces

(-> (nav/parse-string "[1    2]")
    nav/down
    nav/right
    nav/tighten
    nav/root-string)
=> "[1 2]"

tighten-right normalises trailing space

(-> (nav/parse-string "[1 2   ]")
    nav/down
    nav/right
    nav/tighten-right
    nav/root-string)
=> "[1 2]"

4.10    Line information

nav/line-info returns the row and column of the current block based on its position in the source. This is used by code.query and other tools to report locations.

line-info for the first token

(-> (nav/parse-root "(+ 1 2)")
    nav/down
    nav/line-info)
=> (contains {:row 1 :col 1})

line-info tracks newlines

(-> (nav/parse-string "(+\n  1)")
    nav/down
    nav/right
    nav/line-info)
=> (contains {:row 2 :col 3})

5    Layout, Healing, Templating, and Real-World Usage

5.1    Layout overview

block/layout (alias for std.block.layout/layout-main) turns a Clojure form into a formatted block tree. It decides whether each collection fits on one line or needs multi-line formatting, then applies specialised rules for bindings, definitions, pairs, and hiccup-like vectors.

layout keeps a short form on one line

(-> (layout/layout-main '[1 2 3])
    str)
=> "[1 2 3]"

layout splits a long form across lines

(-> (layout/layout-main '[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15])
    str)
=> "[1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n 10\n 11\n 12\n 13\n 14\n 15]"

5.2    Layout special forms

The layout engine recognises defn, let, cond, assoc, case, and many with-* forms. It aligns bindings into columns and indents bodies consistently.

let bindings align in two columns

(-> (layout/layout-main '(let [a 1
                               bbbbbbbbbbbbbbbbbbb 2
                               c 3]
                           (+ a bbbbbbbbbbbbbbbbbbb c)))
    str)
=> "(let [a                   1\n      bbbbbbbbbbbbbbbbbbb 2\n      c                   3]\n  (+ a bbbbbbbbbbbbbbbbbbb c))"

defn indents the body

(-> (layout/layout-main '(defn hello [x]
                           (println x)
                           (+ x 1)))
    str)
=> "(defn hello\n  [x]\n  (println x)\n  (+ x 1))"

5.3    Healing unbalanced delimiters

block/heal parses delimiter pairs and repairs mismatched or missing delimiters. It is used by file watchers and LLM output cleanup to keep source files readable.

heal removes extra closing delimiters

(block/heal "(1 2))")
=> "(1 2)"

heal removes mismatched delimiters

(block/heal "(1 2]}")
=> "(1 2)"

5.4    Rainbow output for debugging

heal/rainbow prints delimiters in alternating colours so you can spot nesting errors. heal/print-rainbow writes the result to *out*.

rainbow wraps output in ANSI codes

(-> (heal/rainbow "(1 (2 (3)))")
    string?)
=> true

rainbow preserves structure

(-> (heal/rainbow "(1 (2))")
    (clojure.string/replace #"\u001B\[[0-9;]*m" ""))
=> "(1 (2))"

5.5    Code templates

std.block.template provides fill-in-the-blank code generation. get-template parses a snippet and finds ~param and ~@param holes. fill-template substitutes values for those holes.

get-template finds parameters

(-> (template/get-template "(defn ~name [~@args] ~body)")
    :params)
=> '[(unquote name) (unquote-splicing args) (unquote body)]

fill-template substitutes values

(let [t (template/get-template "(defn ~name [~@args] ~body)")]
  (template/fill-template t {'name 'greet
                             'args '[x y]
                             'body '(+ x y)}))
=> "(defn greet [x y] (+ x y))"

fill-template with splice

(let [t (template/get-template "[~@items]")]
  (template/fill-template t {'items [1 2 3]}))
=> "[1 2 3]"

5.6    Real-world usage: code.query

code.query is the source-pattern matching layer used across foundation-base. It converts files and strings into std.block.navigate navigators, then matches, selects, and modifies forms with patterns. code.query/context-zloc is the entry point.

code.query selects matching forms

(require '[code.query :as q])
(->> (q/$ {:string "(defn a []) (defn b [])"}
          (defn _ _))
     (map str)
     vec)
=> ["(defn a [])" "(defn b [])"]

code.query replaces matched forms

(require '[std.block.construct :as construct])
(-> (q/$ {:string "(defn a [])"}
         (defn _ _)
         (fn [zloc]
           (nav/replace zloc (construct/block '(defn a [x])))))
    nav/root-string)
=> "(defn a [x])"

5.7    Real-world usage: code.framework

code.framework/analyse-source-code parses an entire source file with nav/parse-root, then uses code.query to find top-level definitions and test facts. This is how the test runner and doc generator understand the codebase.

parse-root mirrors how code.framework loads a file

(-> (nav/parse-root "(ns example)\n\n(defn hello []\n  1)")
    nav/root-string)
=> "(ns example)\n\n(defn hello []\n  1)"

code.query finds top-level defns

(require '[code.query :as q])
(->> (q/$ {:string "(defn a [])\n(defn b [x])"}
          (defn _ _))
     count)
=> 2

5.8    Real-world usage: std.pretty

std.pretty pretty-prints values by routing them through block/layout and then adding rainbow delimiters. This gives the REPL output consistent indentation and visually balanced parentheses.

layout produces pretty-printed output

(-> (layout/layout-main {:a [1 2 3]
                         :b [4 5 6]
                         :c [7 8 9]
                         :d [10 11 12]})
    str)
=> "{:a [1 2 3]\n :b [4 5 6]\n :c [7 8 9]\n :d [10 11 12]}"

rainbow can wrap layout output

(-> (layout/layout-main '(+ 1 (+ 2 (+ 3))))
    str
    heal/rainbow
    string?)
=> true

5.9    Real-world usage: healing and cleanup

std.make.project/file-watcher-heal runs block/heal on changed source files. indigo.server.api_prompt runs LLM-generated DSL output through block/heal before using it. Both rely on the same simple contract: broken string in, balanced string out.

heal removes extra closing delimiters

(block/heal "(:? ()\n    (+ 1))) (+ 2)\n    nil {})")
=> "(:? ()\n    (+ 1) (+ 2)\n    nil {})"

heal cleans up mismatched brackets

(block/heal "(+ 1 2]}")
=> "(+ 1 2)"

5.10    Recipe: a tiny refactoring tool

Combining parsing, navigation, and replacement makes it easy to write small refactorings. The example below rewrites (if (not x) then else) into (if-not x then else).

rewrite if-not

(let [source "(if (not (> x 0)) :small :big)"
      nav    (nav/parse-string source)]
  (-> (nav/replace nav
                   (let [form (nav/value nav)
                         [_ [_ pred] then & else] form]
                     (construct/block (apply list 'if-not pred then else))))
      nav/root-string))
=> "(if-not (> x 0) :small :big)"

insert a docstring placeholder

(-> (nav/parse-string "(defn hello [x])")
    nav/down
    nav/right
    (nav/insert-token-to-right "docstring")
    nav/root-string)
=> "(defn hello \"docstring\" [x])"

6    Reference

6.1    Public façade

std.block interns the most commonly used functions from the sub-namespaces. Most day-to-day work can be done through this single require.



block ^

[elem]
Added 3.0

creates a block

v 3.0
(defn block
  ([elem]
   (cond (base/block? elem)
         elem
         
         (check/token? elem)
         (token elem)

         (check/collection? elem)
         (construct-collection elem)
         
         :else
         (throw (ex-info "Invalid input." {:input elem})))))
link
(base/block-info (block 1)) => {:type :token, :tag :long, :string "1", :height 0, :width 1} (str (block [1 (newline) (void) 2])) => "[1n 2]"

block? ^

[obj]
Added 3.0

check whether an object is a block

v 3.0
(defn block?
  ([obj]
   (instance? IBlock obj)))
link
(block? (construct/void nil)) => true (block? (construct/token "hello")) => true

children ^

[block]
Added 3.0

returns the children in the block

v 3.0
(defn block-children
  ([^IBlockContainer block]
   (._children block)))
link
(->> (block-children (parse/parse-string "[1 2]")) (apply str)) => "1␣␣␣2"

code? ^

[block]
Added 4.0

checks if a block can be representated as code

v 4.0
(defn code-block?
  [block]
  (or (token-block? block)
      (container-block? block)
      (modifier-block? block)))
link
(code-block? (construct/void)) => false (code-block? (construct/token '+)) => true

comment ^

[s]
Added 3.0

creates a comment block

v 3.0
(defn comment
  ([s]
   (if (check/comment? s)
     (type/comment-block s)
     (throw (ex-info "Not a valid comment string." {:input s})))))
link
(str (comment ";hello")) => ";hello"

comment? ^

[block]
Added 3.0

checks if the block is a token block

v 3.0
(defn comment-block?
  ([block]
   (instance? CommentBlock block)))
link
(comment-block? (construct/comment ";;hello")) => true

container ^

[tag children]
Added 3.0

creates a container

v 3.0
(defn container
  ([tag children]
   (let [props (get *container-props* tag)
         _    (container-checks tag children props)]
     (type/container-block tag children props))))
link
(str (container :list [(void) (void)])) => "( )"

container? ^

[block]
Added 3.0

checks if block is a container block

v 3.0
(defn container-block?
  ([block]
   (instance? ContainerBlock block)))
link
(container-block? (construct/block [])) => true

contents ^

[block]
Added 3.0

reads out the contents of a container

v 3.0
(defn contents
  ([block]
   (if (base/expression? block)
     (-> (base/block-children block)
         pr-str
         read-string)
     (-> block
         pr-str
         read-string))))
link
(contents (block [1 2 3])) => '[1 ␣ 2 ␣ 3]

cursor ^

[]
Added 3.0

creates a cursor for the navigator

v 3.0
(defn cursor
  ([]
   (type/modifier-block :hash-cursor "|" conj)))
link
(str (cursor)) => "|"

eof? ^

[block]
Added 3.0

checks if input is an eof block

v 3.0
(defn eof-block?
  ([block]
   (and (void-block? block)
        (= :eof (base/block-tag block)))))
link
(eof-block? (construct/void nil)) => true

expression? ^

[block]
Added 3.0

checks if the block has a value associated with it

v 3.0
(defn expression?
  ([^IBlock block]
   (instance? IBlockExpression block)))
link
(expression? (construct/token 1.2)) => true

heal ^

[content]
Added 4.0

allow multiple passes to heal the delimiter

v 4.0
(defn heal-content
  [content]
  (loop [content content
         retries  50]
    (if (< retries 0)
      content
      (let [new-content (heal-content-single-pass content)]
        (if (= new-content content)
          content
          (recur new-content
                 (dec retries)))))))
link
(b/parse-root (level/heal-content (slurp "test-data/std.block.heal/cases/005_example.block"))) => b/block? (b/parse-root (level/heal-content (slurp "test-data/std.block.heal/cases/004_shorten.block"))) => b/block? (b/parse-root (level/heal-content (slurp "test-data/std.block.heal/cases/003_examples.block"))) => b/block? (b/parse-root (level/heal-content (slurp "test-data/std.block.heal/cases/002_complex.block"))) => b/block?

height ^

[block]
Added 3.0

returns the height of a block

v 3.0
(defn ^Number block-height
  ([^IBlock block]
   (._height block)))
link
(block-height (construct/block ^:list [(construct/newline) (construct/newline)])) => 2

info ^

[block]
Added 3.0

returns the data associated with the block

v 3.0
(defn block-info
  ([^IBlock block]
   {:type     (block-type block)
    :tag      (block-tag block)
    :string   (block-string block)
    :height   (block-height block)
    :width    (block-width block)}))
link
(block-info (construct/token true)) => {:type :token, :tag :boolean, :string "true", :height 0, :width 4} => {:type :token, :tag :boolean, :string "true"} (block-info (construct/void tab)) => {:type :void, :tag :linetab, :string "t", :height 0, :width 4}

layout ^

[form & [opts]]
Added 4.0

performs the main layout

v 4.0
(defn layout-main
  [form & [opts]]
  (binding [common/*layout-fn* layout-default-fn]
    (layout-default-fn form opts)))
link
(construct/get-lines (bind/layout-main '(+ 1 2 3))) => ["(+ 1 2 3)"] (construct/get-lines (bind/layout-main '(let [a {:a 1 :b 2} b {:a 1 :b 2}] (+ a 2)) )) => ["(let [a {:a 1 :b 2}" " b {:a 1 :b 2}]" " (+ a 2))"] (construct/get-lines (bind/layout-main '(let [allowable {:allowable 1 :b 2} b {:a 1 :botherable 2}] (+ a 2)))) => ["(let [allowable {:allowable 1 :b 2}" " b {:a 1 :botherable 2}]" " (+ a 2))"] (construct/get-lines (bind/layout-main '(assoc m :key1 val1 :key2 val2 :key3 (+ a 2)))) => ["(assoc m" " :key1 val1" " :key2 val2" " :key3 (+ a 2))"] (construct/get-lines (bind/layout-main '(hash-map :key1 val1 :key2 val2 :key3 (+ a 2)))) => ["(hash-map :key1 val1" " :key2 val2" " :key3 (+ a 2))"] (construct/get-lines (bind/layout-main '{:a1 {:b1-data-long0 1 :b1-data-long1 2} :a2 {:b2-data-long0 3 :b2-data-long1 4}})) => ["{:a1 {:b1-data-long0 1" " :b1-data-long1 2}" " :a2 {:b2-data-long0 3" " :b2-data-long1 4}}"] (construct/get-lines (bind/layout-main '[[{:a1 {:b1-data-long0 1 :b1-data-long1 2} :a2-long {:b2-data-long0 {:c2-data-long0 6 :c2-data 5} :b2-data-long1 4}}]])) => vector? (construct/get-lines (bind/layout-main '(let [foo-bind [1 2 3] {:keys [a ab abc] :as data} (merge {:a1 {:b1-data 1 :b1-data-long1 2} :a2 {:b2 3 :b2-long1 4}} spec) foo-bind [1 2 3]] (+ 1 2 3)))) => vector? #_#_=> ["(let [foo-bind [1 2 3]" " {:keys [a ab abc]" " :as data} (merge" " {:a1 {:b1-data 1 :b1-data-long1 2}" " :a2 {:b2 3 :b2-long1 4}}" " spec)" " foo-bind [1 2 3]]" " (+ 1 2 3))"] (construct/get-lines (bind/layout-main '(defn layout-with-bindings "layout with bindings" {:added "4.0"} ([form {:keys [indents] :or {indents 0} :as opts}] (let [[start-sym nindents] (layout-multiline-form-setup form opts) start-blocks (list start-sym (construct/space)) bopts (assoc opts :spec {:col-align true :columns 2} :indents nindents) bindings (*layout-fn* (second form) bopts) aopts (assoc opts :indents ( + 1 indents)) arg-spacing (concat [(construct/newline)] (repeat (+ 1 indents) (construct/space))) arg-blocks (->> (drop 2 form) (map (fn [arg] (*layout-fn* arg aopts))) (join-blocks arg-spacing))] (construct/container :list (vec (concat start-blocks [bindings] arg-spacing arg-blocks)))))))) ["(defn layout-with-bindings" " "layout with bindings"" " {:added "4.0"}" " ([form" " {:keys" " [indents]" " :or" " {indents" " 0}" " :as" " opts}] (let [[start-sym" " nindents] (layout-multiline-form-setup form" " opts)" " start-blocks (list start-sym" " (construct/space))" " bopts (assoc opts" " :spec {:col-align true :columns 2}" " :indents nindents)" " bindings (*layout-fn* (second form)" " bopts)" " aopts (assoc opts" " :indents (+ 1 indents))" " arg-spacing (concat [(construct/newline)]" " (repeat (+ 1 indents)" " (construct/space)))" " arg-blocks (->> (drop 2 form)" " (map (fn [arg]" " (*layout-fn* arg aopts))) (join-blocks arg-spacing))]" " (construct/container :list" " (vec (concat start-blocks" " [bindings]" " arg-spacing" " arg-blocks))))))"]

length ^

[block]
Added 3.0

return the length of the string

v 3.0
(defn ^Number block-length
  ([^IBlock block]
   (._length block)))
link
(block-length (construct/void)) => 1 (block-length (construct/block [1 2 3 4])) => 9

linebreak? ^

[block]
Added 3.0

checks if block is of type :linebreak

v 3.0
(defn linebreak-block?
  ([block]
   (and (void-block? block)
        (= :linebreak (base/block-tag block)))))
link
(linebreak-block? (construct/newline)) => true

linespace? ^

[block]
Added 3.0

checks if block is of type :linespace

v 3.0
(defn linespace-block?
  ([block]
   (and (void-block? block)
        (= :linespace (base/block-tag block)))))
link
(linespace-block? (construct/space)) => true

max-width ^

[block] [block offset]
Added 3.0

gets the max width of given block, along with starting offset

v 3.0
(defn max-width
  ([block]
   (max-width block 0))
  ([block offset]
   (cond (= 0 (base/block-height block))
         (base/block-width block)
         
         :else
         (let [counts (->> (base/block-string block)
                           (clojure.string/split-lines)
                           (mapv count))
               counts (if (zero? offset)
                        counts
                        (update counts 0 + offset))]
           
           (- (apply max counts)
              offset)))))
link
[(max-width (parse/parse-string "")) (base/block-info (parse/parse-string ""))] => [0 {:type :void, :tag :eof, :string "", :height 0, :width 0}] [(max-width (parse/parse-root "")) (base/block-info (parse/parse-root ""))] => [0 {:type :collection, :tag :root, :string "", :height 0, :width 0}] (base/block-info (parse/parse-string ""n"")) [(max-width (parse/parse-string ""n"")) (base/block-info (parse/parse-string ""n""))] => [1 {:type :token, :tag :string, :string ""n"", :height 1, :width 1}] (base/block-info (parse/parse-string ""nn"")) => {:type :token, :tag :string, :string ""nn"", :height 2, :width 1} (base/block-info (block [:very-long-line (newline) (void) 2])) => {:type :collection, :tag :vector, :string "[:very-long-linen 2]", :height 1, :width 3} ;; longest line at the start (max-width (block [:very-long-line (newline) (void) 2])) => 16 (max-width (block [:very-long-line (newline) (void) 2]) 10) => 16 ;; longest line in the middle (max-width (block [(newline) (void) :very-long-line (newline) (void) 2])) => 16 (max-width (block [(newline) (void) :very-long-line (newline) (void) 2]) 10) => 6

modifier? ^

[block]
Added 3.0

checks if block is a modifier block

v 3.0
(defn modifier-block?
  ([block]
   (instance? ModifierBlock block)))
link
(modifier-block? (construct/uneval)) => true

newline ^

[]
Added 3.0

creates a newline

v 3.0
(defn newline
  ([] +newline+))
link
(str (newline)) => "\n"

newlines ^

[n]
Added 3.0

creates multiple newlines

v 3.0
(defn newlines
  ([n] (repeat n +newline+)))
link
(apply str (newlines 5)) => "\n\n\n\n\n"

parse-first ^

[string]
Added 4.0

gets the first element in root

v 4.0
(defn parse-first
  ([string]
   (->> (parse-root string)
        (base/block-children)
        (filter type/code-block?)
        (first))))
link
(str (parse-first "a b c")) => "a"

parse-root ^

[s]
Added 3.0

parses a root

v 3.0
(defn parse-root
  ([s]
   (-> (reader/create s)
       (reader/read-repeatedly -parse type/eof-block?)
       (construct/root))))
link
(str (parse-root "a b c")) => "a b c"

parse-string ^

[s]
Added 3.0

parses a block from a string input

v 3.0
(defn parse-string
  ([s]
   (-parse (reader/create s))))
link
(-> (parse-string "#(:b {:b 1})") (base/block-value)) => '(fn* [] ((keyword "b") {(keyword "b") 1}))

prefixed ^

[block]
Added 3.0

returns the length of the starting characters

v 3.0
(defn ^Number block-prefixed
  ([^IBlock block]
   (._prefixed block)))
link
(block-prefixed (construct/block #{})) => 2

root ^

[children]
Added 3.0

constructs a root block

v 3.0
(defn root
  ([children]
   (block (container :root (construct-children children)))))
link
(str (root '[a b])) => "a b"

space ^

[]
Added 3.0

creates a space block

v 3.0
(defn space
  ([] +space+))
link
(str (space)) => "␣"

space? ^

[block]
Added 3.0

checks if block is of type \space

v 3.0
(defn space-block?
  ([block]
   (and (void-block? block)
        (= (.-character ^VoidBlock block) space))))
link
(space-block? (construct/space)) => true

spaces ^

[n]
Added 3.0

creates multiple space blocks

v 3.0
(defn spaces
  ([n] (repeat n +space+)))
link
(apply str (spaces 5)) => "␣␣␣␣␣"

string ^

[block]
Added 3.0

returns block string as representated in the file

v 3.0
(defn ^String block-string
  ([^IBlock block]
   (._string block)))
link
(block-string (construct/token 3/4)) => "3/4" (block-string (construct/void space)) => ""

suffixed ^

[block]
Added 3.0

returns the length of the ending characters

v 3.0
(defn ^Number block-suffixed
  ([^IBlock block]
   (._suffixed block)))
link
(block-suffixed (construct/block #{})) => 1

tab ^

[]
Added 3.0

creates a tab

v 3.0
(defn tab
  ([] (void tab)))
link
(str (tab)) => "\t"

tabs ^

[n]
Added 3.0

creates multiple tabs

v 3.0
(defn tabs
  ([n]
   (repeat n (tab))))
link
(apply str (tabs 5)) => "\t\t\t\t\t"

tag ^

[block]
Added 3.0

returns block tag as keyword

v 3.0
(defn ^clojure.lang.Keyword block-tag
  ([^IBlock block]
   (._tag block)))
link
(block-tag (construct/void nil)) => :eof (block-tag (construct/void space)) => :linespace

token? ^

[block]
Added 3.0

checks if the block is a token block

v 3.0
(defn token-block?
  ([block]
   (instance? TokenBlock block)))
link
(token-block? (construct/token "hello")) => true

type ^

[block]
Added 3.0

returns block type as keyword

v 3.0
(defn ^clojure.lang.Keyword block-type
  ([^IBlock block]
   (._type block)))
link
(block-type (construct/void nil)) => :void (block-type (construct/token "hello")) => :token

uneval ^

[]
Added 3.0

creates a hash-uneval block

v 3.0
(defn uneval
  ([]
   (type/modifier-block :hash-uneval "#_" (fn [accumulator input] accumulator))))
link
(str (uneval)) => "#_"

value ^

[block]
Added 3.0

returns the value of the block

v 3.0
(defn block-value
  ([^IBlockExpression block]
   (._value block)))
link
(block-value (construct/token 1.2)) => 1.2

value-string ^

[block]
Added 3.0

returns the string for which a value will be generated

v 3.0
(defn ^String block-value-string
  ([^IBlockExpression block]
   (._value_string block)))
link
(block-value-string (parse/parse-string "#(+ 1 ::2)")) => "#(+ 1 (keyword ":2"))"

verify ^

[block]
Added 3.0

checks that the block has correct data

v 3.0
(defn ^Boolean block-verify
  ([^IBlock block]
   (._verify block)))
link
(block-verify (construct/token "hello")) => true

void ^

[] [ch]
Added 3.0

creates a void block

v 3.0
(defn void
  ([] +space+)
  ([ch]
   (or (void-lookup ch)
       (let [tag  (check/void-tag ch)
             width (case tag
                     :eof       0
                     :linebreak 0
                     :linetab type/*tab-width*
                     1)
             height (if (= tag :linebreak) 1 0)]
         (if tag
           (type/void-block tag ch width height)
           (throw (ex-info "Not a valid void character." {:input ch})))))))
link
(str (void)) => "␣"

void? ^

[block]
Added 3.0

checks if the block is a void block

v 3.0
(defn void-block?
  ([^IBlock block]
   (instance? VoidBlock block)))
link
(void-block? (construct/void)) => true

width ^

[block]
Added 3.0

returns the width of a block

v 3.0
(defn ^Number block-width
  ([^IBlock block]
   (._width block)))
link
(block-width (construct/token 'hello)) => 5

6.2    Base predicates and accessors

std.block.base defines the block taxonomy and the generic accessors used by every other namespace.



block-children ^

[block]
Added 3.0

returns the children in the block

v 3.0
(defn block-children
  ([^IBlockContainer block]
   (._children block)))
link
(->> (block-children (parse/parse-string "[1 2]")) (apply str)) => "1␣␣␣2"

block-height ^

[block]
Added 3.0

returns the height of a block

v 3.0
(defn ^Number block-height
  ([^IBlock block]
   (._height block)))
link
(block-height (construct/block ^:list [(construct/newline) (construct/newline)])) => 2

block-info ^

[block]
Added 3.0

returns the data associated with the block

v 3.0
(defn block-info
  ([^IBlock block]
   {:type     (block-type block)
    :tag      (block-tag block)
    :string   (block-string block)
    :height   (block-height block)
    :width    (block-width block)}))
link
(block-info (construct/token true)) => {:type :token, :tag :boolean, :string "true", :height 0, :width 4} => {:type :token, :tag :boolean, :string "true"} (block-info (construct/void tab)) => {:type :void, :tag :linetab, :string "t", :height 0, :width 4}

block-length ^

[block]
Added 3.0

return the length of the string

v 3.0
(defn ^Number block-length
  ([^IBlock block]
   (._length block)))
link
(block-length (construct/void)) => 1 (block-length (construct/block [1 2 3 4])) => 9

block-prefixed ^

[block]
Added 3.0

returns the length of the starting characters

v 3.0
(defn ^Number block-prefixed
  ([^IBlock block]
   (._prefixed block)))
link
(block-prefixed (construct/block #{})) => 2

block-string ^

[block]
Added 3.0

returns block string as representated in the file

v 3.0
(defn ^String block-string
  ([^IBlock block]
   (._string block)))
link
(block-string (construct/token 3/4)) => "3/4" (block-string (construct/void space)) => ""

block-suffixed ^

[block]
Added 3.0

returns the length of the ending characters

v 3.0
(defn ^Number block-suffixed
  ([^IBlock block]
   (._suffixed block)))
link
(block-suffixed (construct/block #{})) => 1

block-tag ^

[block]
Added 3.0

returns block tag as keyword

v 3.0
(defn ^clojure.lang.Keyword block-tag
  ([^IBlock block]
   (._tag block)))
link
(block-tag (construct/void nil)) => :eof (block-tag (construct/void space)) => :linespace

block-type ^

[block]
Added 3.0

returns block type as keyword

v 3.0
(defn ^clojure.lang.Keyword block-type
  ([^IBlock block]
   (._type block)))
link
(block-type (construct/void nil)) => :void (block-type (construct/token "hello")) => :token

block-value ^

[block]
Added 3.0

returns the value of the block

v 3.0
(defn block-value
  ([^IBlockExpression block]
   (._value block)))
link
(block-value (construct/token 1.2)) => 1.2

block-value-string ^

[block]
Added 3.0

returns the string for which a value will be generated

v 3.0
(defn ^String block-value-string
  ([^IBlockExpression block]
   (._value_string block)))
link
(block-value-string (parse/parse-string "#(+ 1 ::2)")) => "#(+ 1 (keyword ":2"))"

block-verify ^

[block]
Added 3.0

checks that the block has correct data

v 3.0
(defn ^Boolean block-verify
  ([^IBlock block]
   (._verify block)))
link
(block-verify (construct/token "hello")) => true

block-width ^

[block]
Added 3.0

returns the width of a block

v 3.0
(defn ^Number block-width
  ([^IBlock block]
   (._width block)))
link
(block-width (construct/token 'hello)) => 5

block? ^

[obj]
Added 3.0

check whether an object is a block

v 3.0
(defn block?
  ([obj]
   (instance? IBlock obj)))
link
(block? (construct/void nil)) => true (block? (construct/token "hello")) => true

container? ^

[block]
Added 3.0

determines whether a block has children

v 3.0
(defn container?
  ([^IBlock block]
   (instance? IBlockContainer block)))
link
(container? (parse/parse-string "[1 2 3]")) => true (container? (parse/parse-string " ")) => false

expression? ^

[block]
Added 3.0

checks if the block has a value associated with it

v 3.0
(defn expression?
  ([^IBlock block]
   (instance? IBlockExpression block)))
link
(expression? (construct/token 1.2)) => true

modifier? ^

[block]
Added 3.0

checks if the block is of type INodeModifier

v 3.0
(defn modifier?
  ([^IBlock block]
   (instance? IBlockModifier block)))
link
(modifier? (construct/uneval)) => true

replace-children ^

[block children]
Added 3.0

replace childer

v 3.0
(defn replace-children
  ([^IBlockContainer block children]
   (._replace_children block children)))
link
(->> (replace-children (construct/block []) (conj (vec (block-children (construct/block [1 2]))) (construct/void space) (construct/block [3 4]))) str) => "[1 2 [3 4]]"

6.3    Construction

std.block.construct builds blocks from Clojure data and from raw components.



add-child ^

[container elem]
Added 3.0

adds a child to a container block

v 3.0
(defn add-child
  ([container elem]
   (base/replace-children container
                          (conj (vec (base/block-children container))
                                (block elem)))))
link
(-> (block []) (add-child 1) (add-child 2) (str)) => "[12]"

block ^

[elem]
Added 3.0

creates a block

v 3.0
(defn block
  ([elem]
   (cond (base/block? elem)
         elem
         
         (check/token? elem)
         (token elem)

         (check/collection? elem)
         (construct-collection elem)
         
         :else
         (throw (ex-info "Invalid input." {:input elem})))))
link
(base/block-info (block 1)) => {:type :token, :tag :long, :string "1", :height 0, :width 1} (str (block [1 (newline) (void) 2])) => "[1n 2]"

comment ^

[s]
Added 3.0

creates a comment block

v 3.0
(defn comment
  ([s]
   (if (check/comment? s)
     (type/comment-block s)
     (throw (ex-info "Not a valid comment string." {:input s})))))
link
(str (comment ";hello")) => ";hello"

container ^

[tag children]
Added 3.0

creates a container

v 3.0
(defn container
  ([tag children]
   (let [props (get *container-props* tag)
         _    (container-checks tag children props)]
     (type/container-block tag children props))))
link
(str (container :list [(void) (void)])) => "( )"

contents ^

[block]
Added 3.0

reads out the contents of a container

v 3.0
(defn contents
  ([block]
   (if (base/expression? block)
     (-> (base/block-children block)
         pr-str
         read-string)
     (-> block
         pr-str
         read-string))))
link
(contents (block [1 2 3])) => '[1 ␣ 2 ␣ 3]

cursor ^

[]
Added 3.0

creates a cursor for the navigator

v 3.0
(defn cursor
  ([]
   (type/modifier-block :hash-cursor "|" conj)))
link
(str (cursor)) => "|"

indent-body ^

[block offset]
Added 4.0

indents the body given offset

v 4.0
(defn indent-body
  ([block offset]
   (cond (or (not (type/container-block? block))
             (= 0 (base/block-height block)))
         block
         
         :else
         (-> (line-split block)
             (line-restore offset)))))
link
(str (indent-body (block [1 2 (newline) 3 4]) 5)) => "[1 2n 3 4]"

line-width ^

[block] [block offset]
Added 4.0

get the width at the line which it finishes

v 4.0
(defn line-width
  ([block]
   (line-width block 0))
  ([block offset]
   (cond (= 0 (base/block-height block))
         (base/block-width block)
         
         :else
         (- (base/block-width block) offset))))
link
(line-width (block [:very-long-line (newline) (void) 2])) => 3 (line-width (block [:very-long-line (newline) (void) 2]) 3) => 0

max-width ^

[block] [block offset]
Added 3.0

gets the max width of given block, along with starting offset

v 3.0
(defn max-width
  ([block]
   (max-width block 0))
  ([block offset]
   (cond (= 0 (base/block-height block))
         (base/block-width block)
         
         :else
         (let [counts (->> (base/block-string block)
                           (clojure.string/split-lines)
                           (mapv count))
               counts (if (zero? offset)
                        counts
                        (update counts 0 + offset))]
           
           (- (apply max counts)
              offset)))))
link
[(max-width (parse/parse-string "")) (base/block-info (parse/parse-string ""))] => [0 {:type :void, :tag :eof, :string "", :height 0, :width 0}] [(max-width (parse/parse-root "")) (base/block-info (parse/parse-root ""))] => [0 {:type :collection, :tag :root, :string "", :height 0, :width 0}] (base/block-info (parse/parse-string ""n"")) [(max-width (parse/parse-string ""n"")) (base/block-info (parse/parse-string ""n""))] => [1 {:type :token, :tag :string, :string ""n"", :height 1, :width 1}] (base/block-info (parse/parse-string ""nn"")) => {:type :token, :tag :string, :string ""nn"", :height 2, :width 1} (base/block-info (block [:very-long-line (newline) (void) 2])) => {:type :collection, :tag :vector, :string "[:very-long-linen 2]", :height 1, :width 3} ;; longest line at the start (max-width (block [:very-long-line (newline) (void) 2])) => 16 (max-width (block [:very-long-line (newline) (void) 2]) 10) => 16 ;; longest line in the middle (max-width (block [(newline) (void) :very-long-line (newline) (void) 2])) => 16 (max-width (block [(newline) (void) :very-long-line (newline) (void) 2]) 10) => 6

newline ^

[]
Added 3.0

creates a newline

v 3.0
(defn newline
  ([] +newline+))
link
(str (newline)) => "\n"

newlines ^

[n]
Added 3.0

creates multiple newlines

v 3.0
(defn newlines
  ([n] (repeat n +newline+)))
link
(apply str (newlines 5)) => "\n\n\n\n\n"

root ^

[children]
Added 3.0

constructs a root block

v 3.0
(defn root
  ([children]
   (block (container :root (construct-children children)))))
link
(str (root '[a b])) => "a b"

space ^

[]
Added 3.0

creates a space block

v 3.0
(defn space
  ([] +space+))
link
(str (space)) => "␣"

spaces ^

[n]
Added 3.0

creates multiple space blocks

v 3.0
(defn spaces
  ([n] (repeat n +space+)))
link
(apply str (spaces 5)) => "␣␣␣␣␣"

string-token ^

[form]
Added 3.0

constructs a string token

v 3.0
(defn string-token
  ([form]
   (let [tag     :string
         string  (str """ form """)
         lines   (clojure.string/split-lines string)
         height  (dec (max 1 (count lines)))
         width   (count (last lines))
         width   (max 1 width)]
     #_(type/token-block tag (pr-str form)  form string width height)
     (type/token-block tag string form (pr-str form) width height))))
link
(str (string-token "hello")) => ""hello"" (str (string-token "hellonworld")) => ""hello\nworld"" (str (string-token " "hello" " )) => "" \"hello\" ""

tab ^

[]
Added 3.0

creates a tab

v 3.0
(defn tab
  ([] (void tab)))
link
(str (tab)) => "\t"

tabs ^

[n]
Added 3.0

creates multiple tabs

v 3.0
(defn tabs
  ([n]
   (repeat n (tab))))
link
(apply str (tabs 5)) => "\t\t\t\t\t"

token ^

[form]
Added 3.0

creates a token

v 3.0
(defn token
  ([form]
   (let [tag (check/token-tag form)]
     (cond (= :string tag)
           (string-token form)

           :else
           (let [string (pr-str form)
                 [width height] (token-dimensions tag string)]
             (type/token-block tag string form string width height))))))
link
(str (token 'abc)) => "abc"

token-from-string ^

[string] [string value value-string]
Added 3.0

creates a token from a string input

v 3.0
(defn token-from-string
  ([string]
   (let [value (read-string string)]
     (token-from-string string value string)))
  ([string value value-string]
   (let [tag  (check/token-tag value)
         [width height] (token-dimensions tag string)]
     (type/token-block tag string value (or value-string string) width height))))
link
(str (token-from-string "abc")) => "abc"

uneval ^

[]
Added 3.0

creates a hash-uneval block

v 3.0
(defn uneval
  ([]
   (type/modifier-block :hash-uneval "#_" (fn [accumulator input] accumulator))))
link
(str (uneval)) => "#_"

void ^

[] [ch]
Added 3.0

creates a void block

v 3.0
(defn void
  ([] +space+)
  ([ch]
   (or (void-lookup ch)
       (let [tag  (check/void-tag ch)
             width (case tag
                     :eof       0
                     :linebreak 0
                     :linetab type/*tab-width*
                     1)
             height (if (= tag :linebreak) 1 0)]
         (if tag
           (type/void-block tag ch width height)
           (throw (ex-info "Not a valid void character." {:input ch})))))))
link
(str (void)) => "␣"

6.4    Parsing

std.block.parse converts source strings into block trees.



-parse ^

Added 3.0

extendable parsing function

v 3.0
(defmulti -parse
  (comp #'read-dispatch reader/peek-char))
link
(base/block-info (-parse (reader/create ":a"))) => {:type :token, :tag :keyword, :string ":a", :height 0, :width 2} (base/block-info (-parse (reader/create ""\\n""))) => {:type :token, :tag :string, :string ""\\n"", :height 0, :width 5} (base/block-info (-parse (reader/create ""\n""))) => {:type :token, :tag :string, :string ""\n"", :height 0, :width 4} (base/block-info (-parse (reader/create ""n""))) => {:type :token, :tag :string, :string ""n"", :height 1, :width 1} (base/block-info (-parse (reader/create (slurp "test-data/std.block/cases/001-newlines.example")))) => {:type :void, :tag :linebreak, :string "n", :height 1, :width 0} (base/block-info (-parse (reader/create (slurp "test-data/std.block/cases/002-string-rep.example")))) => {:type :token, :tag :string, :string ""nn"", :height 2, :width 1} (base/block-info (-parse (reader/create (slurp "test-data/std.block/cases/003-newlines-printed.example")))) => {:type :token, :tag :string, :string ""\n"", :height 0, :width 4} (base/block-info (-parse (reader/create (slurp "test-data/std.block/cases/004-newline-with.example")))) => {:type :token, :tag :string, :string ""\nn"", :height 1, :width 1}

delimiter-block? ^

[block]
Added 3.0

checks if block is of tag :delimiter

v 3.0
(defn delimiter-block?
  ([block]
   (= :delimiter (base/block-tag block))))
link
(delimiter-block? (binding [*end-delimiter* (first "}")] (-parse (reader/create "}")))) => true

eof-block? ^

[block]
Added 3.0

checks if block is of tag :eof

v 3.0
(defn eof-block?
  ([block]
   (= :eof (base/block-tag block))))
link
(eof-block? (-parse (reader/create ""))) => true

parse-collection ^

[reader tag]
Added 3.0

parses a collection

v 3.0
(defn parse-collection
  ([reader tag]
   (let [{:keys [start end]} (construct/*container-props* tag)
         children (read-collection reader start (first end))]
     (construct/container tag children))))
link
(-> (parse-collection (reader/create "#(+ 1 2 3 4)") :fn) (base/block-value)) => '(fn* [] (+ 1 2 3 4)) (-> (parse-collection (reader/create "(1 2 3 4)") :list) (base/block-value)) => '(1 2 3 4) (-> (parse-collection (reader/create "[1 2 3 4]") :vector) (base/block-value)) => [1 2 3 4] (-> (parse-collection (reader/create "{1 2 3 4}") :map) (base/block-value)) => {1 2, 3 4} (-> (parse-collection (reader/create "#{1 2 3 4}") :set) (base/block-value)) => #{1 4 3 2}

parse-comment ^

[reader]
Added 3.0

creates a comment

v 3.0
(defn parse-comment
  ([reader]
   (let [line (-> reader
                  (reader/read-until (fn [ch]
                                       (or (nil? ch)
                                           (check/linebreak? ch)))))]
     (construct/comment line))))
link
(-> (reader/create ";this is a comment") parse-comment (base/block-info)) => {:type :comment, :tag :comment, :string ";this is a comment", :height 0, :width 18}

parse-cons ^

[reader tag]
Added 3.0

parses a cons

v 3.0
(defn parse-cons
  ([reader tag]
   (let [{:keys [cons start]} (get construct/*container-props* tag)
         children (read-cons reader start cons)]
     (construct/container tag children))))
link
(-> (parse-cons (reader/create "~hello") :unquote) (base/block-value)) => '(unquote hello) (-> (parse-cons (reader/create "~@hello") :unquote-splice) (base/block-value)) => '(unquote-splicing hello) (-> (parse-cons (reader/create "^tag {:a 1}") :meta) (base/block-value) ((juxt meta identity))) => [{:tag 'tag} {:a 1}] (-> (parse-cons (reader/create "@hello") :deref) (base/block-value)) => '(deref hello) (-> (parse-cons (reader/create "`hello") :syntax) (base/block-value)) => '(quote std.block.parse-test/hello)

parse-first ^

[string]
Added 4.0

gets the first element in root

v 4.0
(defn parse-first
  ([string]
   (->> (parse-root string)
        (base/block-children)
        (filter type/code-block?)
        (first))))
link
(str (parse-first "a b c")) => "a"

parse-hash ^

[reader]
Added 3.0

parses a block starting with `#` from the reader

v 3.0
(defn parse-hash
  ([reader]
   (let [dispatch  (-> reader
                       (read-start "#")
                       (reader/peek-char))
         func      (get *hash-dispatch* dispatch #(parse-cons % :hash-token))]
     (-> reader
         (reader/unread-char #)
         func))))
link
(-> (parse-hash (reader/create "#{1 2 3}")) (base/block-value)) => #{1 2 3} (-> (parse-hash (reader/create "#(+ 1 2)")) (base/block-value)) => '(fn* [] (+ 1 2)) (-> (parse-hash (reader/create "#"hello"")) (base/block-value)) => #"hello" (-> (parse-hash (reader/create "#^hello {}")) (base/block-value)) => (with-meta {} {:tag 'hello}) (-> (parse-hash (reader/create "#'hello")) (base/block-value)) => '(var hello) (-> (parse-hash (reader/create "#=(list 1 2 3)")) (base/block-value)) => '(1 2 3) (-> (parse-hash (reader/create "#?(:clj true)")) (base/block-value)) => '(? {:clj true}) (-> (parse-hash (reader/create "#?@(:clj [1 2 3])")) (base/block-value)) => '(?-splicing {:clj [1 2 3]}) (-> (parse-hash (reader/create "#:hello {:a 1 :b 2}")) (base/block-value)) => #:hello{:b 2, :a 1} (-> (parse-hash (reader/create "#inst "2018-08-06T06:01:40.682-00:00"")) (base/block-value)) => #inst "2018-08-06T06:01:40.682-00:00"

parse-keyword ^

[reader]
Added 3.0

reads a keyword block from the reader

v 3.0
(defn parse-keyword
  ([reader]
   (let [ksym (-> reader
                  (reader/step-char)
                  (reader/read-to-boundary *symbol-allowed*))]
     (construct/token-from-string (str ":" ksym)
                                  (keyword ksym)
                                  (format "(keyword "%s")" (str ksym))))))
link
(-> (reader/create ":a/b") (parse-keyword) (base/block-value)) => :a/b (-> (reader/create "::hello") (parse-keyword) (base/block-value)) => (keyword ":hello")

parse-root ^

[s]
Added 3.0

parses a root

v 3.0
(defn parse-root
  ([s]
   (-> (reader/create s)
       (reader/read-repeatedly -parse type/eof-block?)
       (construct/root))))
link
(str (parse-root "a b c")) => "a b c"

parse-string ^

[s]
Added 3.0

parses a block from a string input

v 3.0
(defn parse-string
  ([s]
   (-parse (reader/create s))))
link
(-> (parse-string "#(:b {:b 1})") (base/block-value)) => '(fn* [] ((keyword "b") {(keyword "b") 1}))

parse-token ^

[reader]
Added 3.0

reads token block from the reader

v 3.0
(defn parse-token
  ([reader]
   (let [start (reader/read-to-boundary reader)
         val   (try (read-string start)
                    (catch Throwable t
                      (reader/throw-reader reader {:value start})))]
     (cond (symbol? val)
           (->> (reader/read-to-boundary reader *symbol-allowed*)
                (str start)
                (construct/token-from-string))

           :else
           (construct/token-from-string start)))))
link
(-> (reader/create "abc") (parse-token) (base/block-value)) => 'abc (-> (reader/create "3/5") (parse-token) (base/block-value)) => 3/5

read-dispatch ^

[ch]
Added 3.0

dispatches on first symbol

v 3.0
(defn read-dispatch
  ([ch]
   (cond (nil? ch) :eof

         (= ch *end-delimiter*) :delimiter

         (check/whitespace? ch) :void

         :else (get *dispatch-options* ch :token))))
link
(read-dispatch tab) => :void (read-dispatch (first "#")) => :hash

6.5    Navigation

std.block.navigate provides the zipper API for editing block trees.



backspace ^

[nav]
Added 3.0

the reverse of insert

v 3.0
(defn backspace
  ([nav]
   (let [nav (-> nav
                 (tighten-left)
                 (delete))]
     (if-let [pnav (left nav)]
       pnav
       nav))))
link
(-> (parse-string "(0 #|1 2 3)") (backspace) str) => "<0,1> (|0 2 3)" (-> (parse-string "( #|1 2 3)") (backspace) str) => "<0,1> (|2 3)"

delete ^

[nav]
Added 3.0

deletes the current element

v 3.0
(defn delete
  ([nav]
   (cond (level-empty? nav)
         (tighten-right nav)

         :else
         (loop [nav (-> nav
                        (position-right)
                        (zip/delete-right))]
           (let [elem (zip/right-element nav)]
             (cond (nil? elem)
                   nav

                   (base/expression? elem)
                   nav

                   :else
                   (recur (zip/delete-right nav))))))))
link
(-> (parse-string "( #|1 2 3)") (delete) str) => "<0,3> ( |2 3)" (-> (parse-string "(1 2 #|3)") (delete) str) => "<0,7> (1 2 |)" (-> (parse-string "( #| )") (delete) str) => "<0,1> (|)"

delete-left ^

[nav]
Added 3.0

deletes left of the current expression

v 3.0
(defn delete-left
  ([nav]
   (cond (level-empty? nav)
         (tighten-left nav)

         :else
         (loop [nav (position-right nav)]
           (let [elem (zip/left-element nav)]
             (cond (nil? elem)
                   nav

                   (base/expression? elem)
                   (zip/delete-left nav)

                   :else
                   (recur (zip/delete-left nav))))))))
link
(-> (parse-string "(1 2 #|3)") (delete-left) str) => "<0,3> (1 |3)" (-> (parse-string "( #|1 2 3)") (delete-left) str) => "<0,1> (|1 2 3)" (-> (parse-string "(#|1 )") (delete-left) str) (-> (parse-string "( 1 #| )") (delete-left) str) (-> (parse-string "( #| )") (delete-left) str) => "<0,1> (|)"

delete-right ^

[nav]
Added 3.0

deletes right of the current expression

v 3.0
(defn delete-right
  ([nav]
   (cond (level-empty? nav)
         (tighten-right nav)

         :else
         (loop [nav (position-left nav)]
           (let [elem (zip/right-element (zip/step-right nav))]
             (cond (nil? elem)
                   (if (type/void-block? (zip/right-element nav))
                     (zip/delete-right nav)
                     nav)

                   (type/void-block? elem)
                   (-> nav
                       (zip/step-right)
                       (zip/delete-right)
                       (zip/step-left)
                       (recur))

                   (type/void-block? (zip/right-element nav))
                   (-> nav
                       (zip/delete-right)
                       (zip/delete-right))

                   :else
                   (-> nav
                       (zip/step-right)
                       (zip/delete-right)
                       (zip/step-left))))))))
link
(-> (parse-string "( #|1 2 3)") (delete-right) str) => "<0,3> ( |1 3)" (-> (parse-string "(1 2 #|3)") (delete-right) str) => "<0,7> (1 2 |3)" (-> (parse-string "( #| 1 )") (delete-right) str) => "<0,1> (| )" (-> (parse-string "( #| )") (delete-right) str) => "<0,1> (|)"

down ^

[nav]
Added 3.0

navigates into the form

v 3.0
(defn down
  ([nav]
   (let [can (zip/can-step-inside? nav)]
     (if can
       (zip/step-inside nav)))))
link
(str (down (from-status [1 (construct/cursor) [2 3]]))) => "<0,4> [1 [|2 3]]"

find-next ^

[nav pred]
Added 4.0

finds the next expression

v 4.0
(defn find-next
  ([nav pred]
   (zip/find-next nav pred)))
link
(-> (parse-string "(#| [[3] @5] )") (find-next (fn [x] (= :deref (base/block-tag x)))) str) => "<0,8> ( [[3] |@5] )"

find-prev ^

[nav pred]
Added 4.0

finds the previous token or whitespace

v 4.0
(defn find-prev
  ([nav pred]
   (zip/find-prev nav pred)))
link
(-> (parse-string "( n n [[3 n]] #| )") (find-prev type/linebreak-block?) (str)) => "<2,5> ( n n [[3 |n]] )"

insert-all ^

[nav [data & more :as arr]]
Added 3.0

inserts all expressions into the block

v 3.0
(defn insert-all
  ([nav [data & more :as arr]]
   (reduce insert-token nav arr)))
link
(-> (parse-string "") (insert-all [1 2 3 4 5 6]) str) => "<0,10> 1␣2␣3␣4␣5␣|6:eof"

insert-newline ^

[nav] [nav num]
Added 3.0

insert newline/s into the block

v 3.0
(defn insert-newline
  ([nav]
   (insert-newline nav 1))
  ([nav num]
   (reduce zip/insert-left nav
           (take num (repeatedly (fn [] (construct/newline)))))))
link
(-> (parse-string "(#|0)") (insert-newline 2) str) => "<2,0> (nn|0)"

insert-space ^

[nav] [nav num]
Added 3.0

insert space/s into the block

v 3.0
(defn insert-space
  ([nav]
   (insert-space nav 1))
  ([nav num]
   (reduce zip/insert-left nav
           (take num (repeatedly (fn [] (construct/space)))))))
link
(-> (parse-string "(#|0)") (insert-space 2) str) => "<0,3> ( |0)"

insert-token ^

[nav data]
Added 4.0

standard insert token

v 4.0
(defn insert-token
  ([nav data]
   (cond (level-empty? nav)
         (insert-empty nav data)

         :else
         (let [nav (position-left nav)]
           (-> nav
               (zip/step-right)
               (zip/insert-left (construct/space))
               (zip/insert-right data))))))
link
(-> (parse-string "(#|0)") (insert-token 1) str) => "<0,3> (0 |1)"

insert-token-to-left ^

[nav data]
Added 3.0

inserts an element to the left

v 3.0
(defn insert-token-to-left
  ([nav data]
   (cond (level-empty? nav)
         (insert-empty nav data)

         :else
         (let [nav (position-left nav)]
           (-> nav
               (zip/insert-left data)
               (zip/insert-left (construct/space)))))))
link
(-> (parse-string "(#|0)") (insert-token-to-left 1) str) => "<0,3> (1 |0)" (-> (parse-string "(#|)") (insert-token-to-left 1) str) => "<0,1> (|1)" (-> (parse-string "( #| )") (insert-token-to-left 1) str) => "<0,1> (|1 )"

insert-token-to-right ^

[nav data]
Added 3.0

inserts an element to the right

v 3.0
(defn insert-token-to-right
  ([nav data]
   (cond (level-empty? nav)
         (insert-empty nav data)

         :else
         (let [nav (position-right nav)]
           (-> nav
               (zip/step-right)
               (zip/insert-right data)
               (zip/insert-right (construct/space))
               (zip/step-left))))))
link
(-> (parse-string "(#|0)") (insert-token-to-right 1) str) => "<0,1> (|0 1)" (-> (parse-string "(#|)") (insert-token-to-right 1) str) => "<0,1> (|1)" (-> (parse-string "( #| )") (insert-token-to-right 1) str) => "<0,1> (|1 )"

left ^

[nav]
Added 3.0

moves to the left expression

v 3.0
(defn left
  ([nav]
   (zip/find-left nav base/expression?)))
link
(-> (parse-string "(1 [1 2 3] #|)") (left) str) => "<0,4> (1 |[1 2 3] )"

left* ^

[nav]
Added 3.0

navigates to left element, including whitespace

v 3.0
(defn left*
  ([nav]
   (zip/step-left nav)))
link
(str (left* (from-status [1 (construct/cursor) 2]))) => "<0,2> [1| 2]"

left-most ^

[nav]
Added 3.0

moves to the left-most expression

v 3.0
(defn left-most
  ([nav]
   (if (nil? (left nav))
     nav
     (recur (left nav)))))
link
(-> (parse-string "(1 [1 2 3] 3 4 #|)") (left-most) str) => "<0,1> (|1 [1 2 3] 3 4 )"

left-most-token ^

[nav]
Added 3.0

moves to the left-most token

v 3.0
(defn left-most-token
  ([nav]
   (if (nil? (left-token nav))
     nav
     (recur (left-token nav)))))
link
(-> (parse-string "(1 {} 2 3 #|4)") (left-most-token) str) => "<0,1> (|1 {} 2 3 4)"

left-token ^

[nav]
Added 3.0

moves to the left token

v 3.0
(defn left-token
  ([nav]
   (zip/find-left nav type/token-block?)))
link
(-> (parse-string "(1 {} #|2 3 4)") (left-token) str) => "<0,1> (|1 {} 2 3 4)"

line-info ^

[nav]
Added 3.0

returns the line info for the current block

v 3.0
(defn line-info
  ([nav]
   (let [block (zip/get nav)
         
         [y x] (:position nav)
         height (base/block-height block)
         width  (base/block-width block)
         [row col] [(inc y) (inc x)]]
     {:row row
      :col col
      :end-row (+ row height)
      :end-col (if (zero? height)
                 (+ col width)
                 (inc width))})))
link
(line-info (parse-string "[1 \n 2 3]")) => {:row 1, :col 1, :end-row 1, :end-col 12} (line-info (parse-string "[1 n 2 3]")) => {:row 1, :col 1, :end-row 2, :end-col 7}

navigator ^

[block] [block opts]
Added 3.0

creates a navigator for the block

v 3.0
(defn navigator
  ([block]
   (navigator block {}))
  ([block opts]
   (zip/zipper (construct/block block)
               (merge zip/+base+
                      {:create-container        construct/empty
                       :create-element          construct/block
                       :cursor                  (construct/cursor)
                       :is-container?           base/container?
                       :is-empty-container?     (comp empty? base/block-children)
                       :is-element?             base/block?
                       :list-elements           base/block-children
                       :add-element             construct/add-child
                       :update-elements         base/replace-children
                       :update-step-left        update-step-left
                       :update-step-right       update-step-right
                       :update-step-inside      update-step-inside
                       :update-step-inside-left update-step-inside-left
                       :update-step-outside     update-step-outside
                       :update-delete-left      update-step-left
                       :update-delete-right     (fn [zip elem] zip)
                       :update-insert-left      update-step-right
                       :update-insert-right     (fn [zip elem] zip)})
               {:tag :navigator
                :prefix ""
                :display display-navigator
                :position [0 0]})))
link
(str (navigator [1 2 3 4])) => "<0,0> |[1 2 3 4]"

navigator? ^

[obj]
Added 3.0

checks if object is navigator

v 3.0
(defn navigator?
  ([obj]
   (and (instance? std.lib.zip.Zipper obj)
        (= (:tag obj) :navigator))))
link
(navigator? (navigator [1 2 3 4])) => true

next ^

[nav]
Added 3.0

moves to the next expression

v 3.0
(defn next
  ([nav]
   (zip/find-next nav base/expression?)))
link
(-> (parse-string "(#| [[3]] )") (next) (next) (next) str) => "<0,5> ( [[|3]] )"

next-token ^

[nav]
Added 3.0

moves to the next token

v 3.0
(defn next-token
  ([nav]
   (zip/find-next nav type/token-block?)))
link
(-> (parse-string "(#|[[1 2 3 4]])") (next-token) str) => "<0,3> ([[|1 2 3 4]])"

parse-first ^

[string]
Added 4.0

parses the navigator from the first form

v 4.0
(defn parse-first
  ([string]
   (navigator (parse/parse-first string))))
link
(str (parse-first "a b c")) => "<0,0> |a" (str (parse-first "(+ 1 2 3) (+ 4 5)")) => "<0,0> |(+ 1 2 3)"

parse-root ^

[string]
Added 3.0

parses the navigator from root string

v 3.0
(defn parse-root
  ([string]
   (navigator (parse/parse-root string))))
link
(str (parse-root "a b c")) => "<0,0> |a b c"

parse-string ^

[string]
Added 3.0

creates a navigator from string

v 3.0
(defn parse-string
  ([string]
   (from-status (parse/parse-string string))))
link
(str (parse-string "(2 #| 3 )")) => "<0,5> (2 | 3 )"

prev ^

[nav]
Added 3.0

moves to the previous expression

v 3.0
(defn prev
  ([nav]
   (zip/find-prev nav base/expression?)))
link
(-> (parse-string "([1 2 [3]] #|)") (prev) str) => "<0,7> ([1 2 [|3]] )"

prev-token ^

[nav]
Added 3.0

moves to the previous token

v 3.0
(defn prev-token
  ([nav]
   (zip/find-prev nav type/token-block?)))
link
(-> (parse-string "(1 (2 3 [4])#|)") (prev-token) str) => "<0,9> (1 (2 3 [|4]))"

replace ^

[nav data]
Added 3.0

replaces an element at the cursor

v 3.0
(defn replace
  ([nav data]
   (cond (level-empty? nav)
         (throw (ex-info "Replace failed - level empty." {:nav nav}))

         :else
         (let [nav (position-right nav)]
           (cond (base/expression? (zip/right-element nav))
                 (zip/replace-right nav data)

                 :else
                 (throw (ex-info "Replace failed - no element" {:nav nav})))))))
link
(-> (parse-string "(0 #|1 2 3)") (position-right) (replace :a) str) => "<0,4> (0 |:a 2 3)"

replace-splice ^

[nav data]
Added 3.0

replaces an element at the cursor

v 3.0
(defn replace-splice
  ([nav data]
   (cond (level-empty? nav)
         (throw (ex-info "Replace failed - level empty." {:nav nav}))

         :else
         (let [nav (position-right nav)]
           (cond (base/expression? (zip/right-element nav))
                 (delete
                  (reduce (fn [nav item]
                            (insert-token-to-right nav item))
                          nav
                          (reverse data)))
                 
                 :else
                 (throw (ex-info "Replace failed - no element" {:nav nav})))))))
link
(-> (parse-string "(0 #|1 2 3)") (position-right) (replace-splice [:a :b :c]) str) => "<0,4> (0 |:a :b :c 2 3)"

right ^

[nav]
Added 3.0

moves to the expression on the right

v 3.0
(defn right
  ([nav]
   (zip/find-right nav base/expression?)))
link
(-> (parse-string "(#|[1 2 3] 3 4 ) ") (right) str) => "<0,10> ([1 2 3] |3 4 )"

right* ^

[nav]
Added 3.0

navigates to right element, including whitespace

v 3.0
(defn right*
  ([nav]
   (zip/step-right nav)))
link
(str (right* (from-status [(construct/cursor) 1 2]))) => "<0,2> [1| 2]"

right-most ^

[nav]
Added 3.0

moves to the right-most expression

v 3.0
(defn right-most
  ([nav]
   (if (nil? (right nav))
     nav
     (recur (right nav)))))
link
(-> (parse-string "(#|[1 2 3] 3 4 ) ") (right-most) str) => "<0,12> ([1 2 3] 3 |4 )"

right-most-token ^

[nav]
Added 3.0

moves to the right-most token

v 3.0
(defn right-most-token
  ([nav]
   (if (nil? (right-token nav))
     nav
     (recur (right-token nav)))))
link
(-> (parse-string "(#|1 {} 2 3 [4])") (right-most-token) str) => "<0,10> (1 {} 2 |3 [4])"

right-token ^

[nav]
Added 3.0

moves to the right token

v 3.0
(defn right-token
  ([nav]
   (zip/find-right nav type/token-block?)))
link
(-> (parse-string "(#|1 {} 2 3 4)") (right-token) str) => "<0,8> (1 {} |2 3 4)"

root-string ^

[nav]
Added 3.0

returns the top level string

v 3.0
(defn root-string
  ([nav]
   (->> nav
        (zip/step-outside-most)
        (zip/step-left-most)
        (zip/right-elements)
        (map base/block-string)
        (apply str))))
link
(root-string (navigator [1 2 3 4])) => "[1 2 3 4]"

swap ^

[nav f]
Added 3.0

applies a function to the element at the current cursor position, replacing it with the result

v 3.0
(defn swap
  ([nav f]
   (cond (level-empty? nav)
         (throw (ex-info "Replace failed - level empty." {:nav nav}))

         :else
         (let [nav  (position-right nav)
               prev (value nav)]
           (cond (base/expression? (zip/right-element nav))
                 (zip/replace-right nav (f prev))

                 :else
                 (throw (ex-info "Replace failed - no element" {:nav nav})))))))
link
(-> (parse-string "(0 #|1 2 3)") (position-right) (swap inc) str) => "<0,4> (0 |2 2 3)"

tighten ^

[nav]
Added 3.0

removes extra spaces on both the left and right

v 3.0
(defn tighten
  ([nav]
   (-> nav
       (tighten-left)
       (tighten-right))))
link
(-> (parse-string "(1 2 #|3 4)") (tighten) str) => "<0,5> (1 2 |3 4)"

tighten-left ^

[nav]
Added 3.0

removes extra spaces on the left

v 3.0
(defn tighten-left
  ([nav]
   (loop [nav  (position-right nav)]
     (let [elem (zip/left-element nav)]
       (cond (nil? elem)
             nav

             (type/void-block? elem)
             (recur (zip/delete-left nav))

             (type/comment-block? elem)
             (zip/insert-left nav (construct/newline))

             :else
             (zip/insert-left nav (construct/space)))))))
link
(-> (parse-string "(1 2 3 #|4)") (tighten-left) str) => "<0,7> (1 2 3 |4)" (-> (parse-string "(1 2 3 #| 4)") (tighten-left) str) => "<0,7> (1 2 3 |4)" (-> (parse-string "( #| )") (tighten-left) str) => "<0,1> (|)"

tighten-right ^

[nav]
Added 3.0

removes extra spaces on the right

v 3.0
(defn tighten-right
  ([nav]
   (loop [nav (position-left nav)]
     (let [elem (zip/right-element (zip/step-right nav))]
       (cond (nil? elem)
             (if (type/void-block? (zip/right-element nav))
               (zip/delete-right nav)
               nav)

             (type/void-block? elem)
             (-> (zip/step-right nav)
                 (zip/delete-right)
                 (zip/step-left)
                 (recur))

             :else
             (-> (zip/step-right nav)
                 (zip/insert-right (construct/space))
                 (zip/step-left)))))))
link
(-> (parse-string "(1 2 #|3 4)") (tighten-right) str) => "<0,5> (1 2 |3 4)" (-> (parse-string "(1 2 3 #| 4)") (tighten-right) str) => "<0,5> (1 2 |3 4)" (-> (parse-string "( #| )") (tighten-right) str) => "<0,1> (|)"

up ^

[nav]
Added 3.0

navigates outside of the form

v 3.0
(defn up
  ([nav]
   (zip/step-outside nav)))
link
(str (up (from-status [1 [2 (construct/cursor) 3]]))) => "<0,3> [1 |[2 3]]"

update-children ^

[nav children]
Added 3.0

replaces the current children

v 3.0
(defn update-children
  ([nav children]
   (let [elem (zip/right-element nav)
         nelem (base/replace-children elem children)]
     (zip/replace-right nav nelem))))
link
(-> (update-children (parse-string "[1 2 3]") [(construct/block 4) (construct/space) (construct/block 5)]) str) => "<0,0> |[4 5]"

6.6    Layout and healing

std.block.layout formats forms; std.block.heal repairs delimiters.



layout-annotate ^

[form]
Added 4.0

adds metadata annotation to form

v 4.0
(defn layout-annotate
  [form]
  (cond (c/form? form)
        (cond ('#{fn} (first form))
              (layout-annotate-fn-anon form)

              (is-def? (first form))
              (layout-annotate-fn-named form)
              
              (is-binding? (first form))
              (let [[sym bindings & more] form]
                (apply list
                       sym
                       (layout-annotate-bindings bindings)
                       more))

              :else form)

        (and (vector? form)
             (= :path (first form)))
        (layout-annotate-svg-path form)
        
        :else form))
link
(binding [*print-meta* true] (pr-str (bind/layout-annotate '(let [{:keys [a b c d e] :as other} b 2])))) => "(let ^{:spec {:columns 2, :col-align true}} [^{:readable-len 30} {:keys ^{:tag :vector, :readable-len 10, :spec {:columns 1}} [a b c d e], :as other} b])"

layout-default-fn ^

[form opts]
Added 4.0

the default function for level 1 transformation

v 4.0
(defn layout-default-fn
  [form opts]
  (let [is-multiline (est/estimate-multiline form {})
        {:keys [metadata
                spec]}  (if (coll? form)
                          (meta form))
        spec  (or spec (:spec opts) (layout-spec-fn form is-multiline))
        form  (layout-annotate form)
        nopts (assoc opts :spec spec)]
    (cond (not is-multiline) (construct/block form)
          
          (map? form) (common/layout-multiline-hashmap form nopts)

          (set? form) (common/layout-multiline-hashset form nopts)
          
          (vector? form) (if (layout-hiccup-like form)
                           (common/layout-multiline-hiccup form nopts)
                           (common/layout-multiline-vector form nopts))

          (c/form? form) (common/layout-multiline-list form nopts)
          
          :else (construct/block form))))
link
(construct/rep (bind/layout-default-fn [1 2 3] {})) => [1 2 3]

layout-hiccup-like ^

[v]
Added 4.0

checks if form is hiccup structure

v 4.0
(defn layout-hiccup-like
  [v]
  (and (vector? v)
       (or (and (= :% (first v))
                (symbol? (second v)))
           (and (= :<> (first v))
                (some vector? (rest v)))
           (and (keyword? (first v))
                (or (map? (second v))
                    (vector? (second v)))
                (and (every? (comp not keyword?) (rest (rest v)))
                     (every? (comp not map?) (rest (rest v))))))))
link
(bind/layout-hiccup-like [:a :b :c]) => false (bind/layout-hiccup-like [:a [:b [:c]]]) => true (bind/layout-hiccup-like [:a {:x 1 :y 2} [:b [:c]]]) => true (bind/layout-hiccup-like '[:% hello [:b [:c]]]) => true (bind/layout-hiccup-like '[:<> [:b [:c]] [:b [:c]]]) => true

layout-main ^

[form & [opts]]
Added 4.0

performs the main layout

v 4.0
(defn layout-main
  [form & [opts]]
  (binding [common/*layout-fn* layout-default-fn]
    (layout-default-fn form opts)))
link
(construct/get-lines (bind/layout-main '(+ 1 2 3))) => ["(+ 1 2 3)"] (construct/get-lines (bind/layout-main '(let [a {:a 1 :b 2} b {:a 1 :b 2}] (+ a 2)) )) => ["(let [a {:a 1 :b 2}" " b {:a 1 :b 2}]" " (+ a 2))"] (construct/get-lines (bind/layout-main '(let [allowable {:allowable 1 :b 2} b {:a 1 :botherable 2}] (+ a 2)))) => ["(let [allowable {:allowable 1 :b 2}" " b {:a 1 :botherable 2}]" " (+ a 2))"] (construct/get-lines (bind/layout-main '(assoc m :key1 val1 :key2 val2 :key3 (+ a 2)))) => ["(assoc m" " :key1 val1" " :key2 val2" " :key3 (+ a 2))"] (construct/get-lines (bind/layout-main '(hash-map :key1 val1 :key2 val2 :key3 (+ a 2)))) => ["(hash-map :key1 val1" " :key2 val2" " :key3 (+ a 2))"] (construct/get-lines (bind/layout-main '{:a1 {:b1-data-long0 1 :b1-data-long1 2} :a2 {:b2-data-long0 3 :b2-data-long1 4}})) => ["{:a1 {:b1-data-long0 1" " :b1-data-long1 2}" " :a2 {:b2-data-long0 3" " :b2-data-long1 4}}"] (construct/get-lines (bind/layout-main '[[{:a1 {:b1-data-long0 1 :b1-data-long1 2} :a2-long {:b2-data-long0 {:c2-data-long0 6 :c2-data 5} :b2-data-long1 4}}]])) => vector? (construct/get-lines (bind/layout-main '(let [foo-bind [1 2 3] {:keys [a ab abc] :as data} (merge {:a1 {:b1-data 1 :b1-data-long1 2} :a2 {:b2 3 :b2-long1 4}} spec) foo-bind [1 2 3]] (+ 1 2 3)))) => vector? #_#_=> ["(let [foo-bind [1 2 3]" " {:keys [a ab abc]" " :as data} (merge" " {:a1 {:b1-data 1 :b1-data-long1 2}" " :a2 {:b2 3 :b2-long1 4}}" " spec)" " foo-bind [1 2 3]]" " (+ 1 2 3))"] (construct/get-lines (bind/layout-main '(defn layout-with-bindings "layout with bindings" {:added "4.0"} ([form {:keys [indents] :or {indents 0} :as opts}] (let [[start-sym nindents] (layout-multiline-form-setup form opts) start-blocks (list start-sym (construct/space)) bopts (assoc opts :spec {:col-align true :columns 2} :indents nindents) bindings (*layout-fn* (second form) bopts) aopts (assoc opts :indents ( + 1 indents)) arg-spacing (concat [(construct/newline)] (repeat (+ 1 indents) (construct/space))) arg-blocks (->> (drop 2 form) (map (fn [arg] (*layout-fn* arg aopts))) (join-blocks arg-spacing))] (construct/container :list (vec (concat start-blocks [bindings] arg-spacing arg-blocks)))))))) ["(defn layout-with-bindings" " "layout with bindings"" " {:added "4.0"}" " ([form" " {:keys" " [indents]" " :or" " {indents" " 0}" " :as" " opts}] (let [[start-sym" " nindents] (layout-multiline-form-setup form" " opts)" " start-blocks (list start-sym" " (construct/space))" " bopts (assoc opts" " :spec {:col-align true :columns 2}" " :indents nindents)" " bindings (*layout-fn* (second form)" " bopts)" " aopts (assoc opts" " :indents (+ 1 indents))" " arg-spacing (concat [(construct/newline)]" " (repeat (+ 1 indents)" " (construct/space)))" " arg-blocks (->> (drop 2 form)" " (map (fn [arg]" " (*layout-fn* arg aopts))) (join-blocks arg-spacing))]" " (construct/container :list" " (vec (concat start-blocks" " [bindings]" " arg-spacing" " arg-blocks))))))"]

layout-spec-fn ^

[form is-multiline]
Added 4.0

gets the layout spec

v 4.0
(defn layout-spec-fn
  [form is-multiline]
  (if is-multiline
    (cond (c/form? form)
          (cond (coll? (first form))
                {:col-from  0
                 :col-start 1
                 :col-call true}

                (= 'catch (first form))
                {:col-from 2
                 :col-start 2}
                
                (= 'cond (first form))
                {:columns 2
                 :col-from 0
                 :col-align true
                 :col-call true}

                (= 'assoc (first form))
                {:columns 2
                 :col-from 1
                 :col-call true}

                (= 'hash-map (first form))
                {:columns 2
                 :col-from 0}                
                
                (is-binding? (first form))
                {:col-from 1
                 :col-start 2}

                (is-def? (first form))
                {:col-from 1
                 :col-start 2}

                (+pairing+ (first form))
                {:columns 2
                 :col-from 1
                 :col-start 2
                 :col-align true
                 :col-call true}

                :else
                {:col-from  1
                 :col-call true}))))
link
(bind/layout-spec-fn '(assoc m :a 1 :b 2) true) => {:columns 2, :col-from 1 :col-call true}


heal ^

[content]
Added 4.0

allow multiple passes to heal the delimiter

v 4.0
(defn heal-content
  [content]
  (loop [content content
         retries  50]
    (if (< retries 0)
      content
      (let [new-content (heal-content-single-pass content)]
        (if (= new-content content)
          content
          (recur new-content
                 (dec retries)))))))
link
(b/parse-root (level/heal-content (slurp "test-data/std.block.heal/cases/005_example.block"))) => b/block? (b/parse-root (level/heal-content (slurp "test-data/std.block.heal/cases/004_shorten.block"))) => b/block? (b/parse-root (level/heal-content (slurp "test-data/std.block.heal/cases/003_examples.block"))) => b/block? (b/parse-root (level/heal-content (slurp "test-data/std.block.heal/cases/002_complex.block"))) => b/block?

print-rainbow ^

[content]
Added 4.1

prints the content with rainbow parens

v 4.1
(defn print-rainbow
  [content]
  (print/print-rainbow
   content
   (parse/pair-delimiters
    (parse/parse-delimiters content))))
link
(env/with-out-str (print-rainbow "(+ 12 3)")) => "(+ 12 3)"

rainbow ^

[content]
Added 4.1

formats the content with rainbow parens

v 4.1
(defn rainbow
  [content]
  (env/with-out-str
    (print-rainbow content)))
link
(rainbow "(+ 12 3)") => "(+ 12 3)"

6.7    Templates

std.block.template generates code from snippets with unquote holes.



fill-template ^

[template input]
Added 4.0

fills out the template

v 4.0
(defn fill-template
  [template input]
  (let [{:keys [code
                params
                input-fn]} template
        input (input-fn input)
        missing  (set/difference (set (map second params))
                                 (set (keys input)))
        _ (when (not-empty missing)
            (f/error "Missing params: " {:missing missing
                                         :input input}))]
    (nav/root-string
     (reduce (fn [nav param]
               
               (cond-> (nav/find-next-token nav param)
                 (= 'unquote (first param)) (nav/replace (get input (second param)))
                 (= 'unquote-splicing (first param)) (nav/replace-splice (get input (second param)))))
             (nav/navigator code)
             params))))
link
(clojure.string/split-lines (gen/fill-template (gen/get-template TEMPLATE_QUERY) '{return :json meta-entry {:sb/grant :all} sym hello name hello-name hello (1 2 3)})) => ["(defn.pg ^{:%% :sql" " :- :json" " :api/meta {:sb/grant :all}}" " hello" " []" " (hello-name (szndb.core.fn-util/auth-uid) 1 2 3))"]

get-template ^

[code-str & [input-fn multi]]
Added 4.0

gets the template

v 4.0
(defn get-template
  [code-str & [input-fn multi]]
  (let [code   (if multi
                 (b/parse-root code-str)
                 (b/parse-first code-str))
        params (get-template-params code)]
    {:code code
     :params params
     :input-fn (or input-fn identity)
     :multi multi}))
link
(gen/get-template TEMPLATE_QUERY) => map?

get-template-params ^

[code]
Added 4.0

gets the template params in the query

v 4.0
(defn get-template-params
  [code]
  (->> (nav/navigator code)
       (iterate (fn [nav]
                  (nav/find-next nav
                                 (fn [block]
                                   (or (= :unquote-splice (:tag (b/info block)))
                                       (= :unquote (:tag (b/info block))))))))
       (drop 1)
       (take-while identity)
       (map nav/value)))
link
(gen/get-template-params (b/parse-first TEMPLATE_QUERY)) => '((unquote return) (unquote meta-entry) (unquote sym) (unquote name) (unquote-splicing hello))

7    std.block Guide

std.block allows you to treat source code as data structure ("blocks") that preserves formatting, whitespace, and comments. This is essential for building formatters, linters, and refactoring tools.

7.1    Core Concepts

  • Block: The atom of the system. Can be a token (symbol, keyword), a container (list, vector), or void (whitespace, comment).n- Construct: Functions to build blocks programmatically.n- Layout: Engine to render blocks back to string with specific formatting rules.

7.2    Usage

7.2.1    Scenarios

7.2.1.1    1. Programmatic Code Generation

Instead of building code with lists and then pretty-printing (which loses control over exact formatting), you can build blocks.

(require '[std.block :as block])\n\n;; Create a `(+ 1 2)` block\n(def b (block/container :list\n         [(block/token '+)\n          (block/space)\n          (block/token 1)\n          (block/space)\n          (block/token 2)]))\n\n(block/string b) ;; => \"(+ 1 2)\"

7.2.1.2    2. Layout Customization

You can control how a block is printed by setting its layout spec.

;; Create a vector that MUST be on one line\n(def v (block/container :vector\n         [(block/token 1) (block/space) (block/token 2)]))\n\n;; Layout usually decides based on width, but we can force it?\n;; Note: Layout logic is internal, but you can inspect the result of `layout`.\n\n(block/string (block/layout v))

To implement custom formatting rules (like cond or let binding alignment), you would typically extend the std.block.layout multimethods or manipulate the block structure before layout.

7.2.1.3    3. Parsing and Analysis

Distinguishing between code and "void" (whitespace/comments) is key for analysis tools.

(def root (block/parse-string \"(+ 1 1) ;; comment\"))\n\n(def children (block/children root))\n\n;; Filter only meaningful code\n(filter block/code? children)\n;; => (#blk{:string \"(+ 1 1)\" ...})\n\n;; Find comments\n(filter block/comment? children)\n;; => (#blk{:string \";; comment\" ...})

7.2.1.4    4. Manipulating the Block Tree

You can traverse and modify the block tree manually, although code.query provides a higher-level API for this.

;; Replace all occurrences of 1 with 2 in a block\n(defn replace-one [blk]\n  (if (and (block/token? blk) (= (block/value blk) 1))\n    (block/token 2)\n    (if (block/container? blk)\n      (update blk :children #(mapv replace-one %))\n      blk)))

7.2.1.5    5. Handling Uneval forms

#_ (uneval) forms are tricky in standard Clojure readers as they disappear. std.block preserves them.

(def b (block/parse-string \"#_(+ 1 2)\"))\n(block/type b) ;; => :uneval (or similar modifier type)

8    std.block.base Tutorial

Module: std.block.basenSource File: src/std/block/base.cljnTest File: test/std/block/base_test.clj

The std.block.base module defines the fundamental protocols and core functions for interacting with std.block AST nodes. It establishes the basic building blocks and properties that all block types share, such as type, tag, string representation, and dimensions.

8.1    Core Concepts

  • IBlock Protocol: The foundational protocol that all std.block nodes implement. It defines the basic interface for querying block properties.n IBlockExpression Protocol: Extends IBlock for blocks that have an associated Clojure value.n IBlockModifier Protocol: Extends IBlock for blocks that modify an accumulator (e.g., unevaluated forms).n IBlockContainer Protocol: Extends IBlock for blocks that can contain other blocks (e.g., lists, vectors, maps).n *block-types*: A dynamic var containing a set of all recognized block types (:void, :token, :comment, :container, :modifier).n *container-tags*: A dynamic var mapping container types to their specific tags (e.g., :collection to #{:list :map :set :vector}).n *block-tags*: A dynamic var merging *container-tags* with tags for other block types.n *void-representations*: A dynamic var mapping special characters (like &#92;space, &#92;newline) to their void block tags.n *container-limits*: A dynamic var defining the start and end delimiters for various container types (e.g., {:list {:start "(" :end ")"}}).

8.2    Functions

8.2.1    block?

^{:refer std.block.base/block? :added "3.0"}

Checks whether an object is an IBlock instance.

block? example

(block? (construct/void nil))
=> true

(block? (construct/token "hello"))
=> true

8.2.2    block-type

^{:refer std.block.base/block-type :added "3.0"}

Returns the block's type as a keyword (e.g., :void, :token, :container).

block-type example

(block-type (construct/void nil))
=> :void

(block-type (construct/token "hello"))
=> :token

8.2.3    block-tag

^{:refer std.block.base/block-tag :added "3.0"}

Returns the block's specific tag as a keyword (e.g., :eof, :linespace, :symbol).

block-tag example

(block-tag (construct/void nil))
=> :eof

(block-tag (construct/void \space))
=> :linespace

8.2.4    block-string

^{:refer std.block.base/block-string :added "3.0"}

Returns the raw string representation of the block as it would appear in the source file.

block-string example

(block-string (construct/token 3/4))
=> "3/4"

(block-string (construct/void \space))
=> " "

8.2.5    block-length

^{:refer std.block.base/block-length :added "3.0"}

Returns the total character length of the block's string representation.

block-length example

(block-length (construct/void))
=> 1

(block-length (construct/block [1 2 3 4]))
=> 9
;; (e.g., "[1 2 3 4]")

8.2.6    block-width

^{:refer std.block.base/block-width :added "3.0"}

Returns the visual width of the block (number of characters on a single line).

block-width example

(block-width (construct/token 'hello))
=> 5

8.2.7    block-height

^{:refer std.block.base/block-height :added "3.0"}

Returns the height of the block (number of lines it spans).

block-height example

(block-height (construct/block
               ^:list [(construct/newline)
                       (construct/newline)]))
=> 2

8.2.8    block-prefixed

^{:refer std.block.base/block-prefixed :added "3.0"}

Returns the length of any starting characters (e.g., ( for a list, [ for a vector).

block-prefixed example

(block-prefixed (construct/block #{}))
=> 2
;; (e.g., for a set like #{})

8.2.9    block-suffixed

^{:refer std.block.base/block-suffixed :added "3.0"}

Returns the length of any ending characters (e.g., ) for a list, ] for a vector).

block-suffixed example

(block-suffixed (construct/block #{}))
=> 1
;; (e.g., for a set like #{})

8.2.10    block-verify

^{:refer std.block.base/block-verify :added "3.0"}

Checks that the block has correct internal data and structure.

block-verify example

Example from test code, but no direct assertion provided.
This function likely returns true for valid blocks.
(block-verify (construct/token "valid"))
=> true

8.2.11    expression?

^{:refer std.block.base/expression? :added "3.0"}

Checks if the block has a Clojure value associated with it (i.e., implements IBlockExpression).

expression? example

(expression? (construct/token 1.2))
=> true

8.2.12    block-value

^{:refer std.block.base/block-value :added "3.0"}

Returns the actual Clojure value represented by an expression block.

block-value example

(block-value (construct/token 1.2))
=> 1.2

8.2.13    block-value-string

^{:refer std.block.base/block-value-string :added "3.0"}

Returns the string representation from which the block's value was generated. This can differ from block-string for special forms.

block-value-string example

(block-value-string (parse/parse-string "#(+ 1 ::2)"))
=> "#(+ 1 (keyword ":2"))"

8.2.14    modifier?

^{:refer std.block.base/modifier? :added "3.0"}

Checks if the block is of type IBlockModifier.

modifier? example

(modifier? (construct/uneval))
=> true

8.2.15    block-modify

^{:refer std.block.base/block-modify :added "3.0"}

Allows a modifier block to modify an accumulator. Used in parsing and transformation.

block-modify example

(block-modify (construct/uneval) [1 2] 'ANYTHING)
=> [1 2]

8.2.16    container?

^{:refer std.block.base/container? :added "3.0"}

Determines whether a block has children (i.e., implements IBlockContainer).

container? example

(container? (parse/parse-string "[1 2 3]"))
=> true

(container? (parse/parse-string " "))
=> false

8.2.17    block-children

^{:refer std.block.base/block-children :added "3.0"}

Returns a sequence of child blocks within a container block.

block-children example

(->> (block-children (parse/parse-string "[1   2]"))
     (map block-string))
=> ("1" "   " "2")

8.2.18    replace-children

^{:refer std.block.base/replace-children :added "3.0"}

Replaces the children of a container block with a new sequence of children.

replace-children example

(->> (replace-children (construct/block [])
                       (conj (vec (block-children (construct/block [1 2])))
                             (construct/void \space)
                             (construct/block [3 4])))
     str)
=> "[1 2 [3 4]]"

8.2.19    block-info

^{:refer std.block.base/block-info :added "3.0"}

Returns a map containing basic information about the block, including its type, tag, string, height, and width.

block-info example

(block-info (construct/token true))
=> {:type :token, :tag :boolean, :string "true", :height 0, :width 4}

(block-info (construct/void \tab))
=> {:type :void, :tag :linetab, :string "\t", :height 0, :width 4}

9    std.block.check Tutorial

Module: std.block.checknSource File: src/std/block/check.cljnTest File: test/std/block/check_test.clj

The std.block.check module provides utility functions for classifying characters and Clojure forms based on their syntactic role within std.block's parsing and construction logic. It defines predicates for various types of characters (whitespace, delimiters, linebreaks) and forms (tokens, collections, void elements).

9.1    Core Concepts

  • *boundaries*: A dynamic var containing a set of characters that act as boundaries in Clojure syntax (e.g., space, colon, semicolon, parentheses, brackets, braces).n *linebreaks*: A dynamic var containing a set of characters that represent line breaks (&#92;newline, &#92;return, &#92;formfeed).n *delimiters*: A dynamic var containing a set of characters that act as delimiters for collections (}, ], ), (, [, {).n *void-checks*: A dynamic var mapping void block tags (e.g., :eof, :linetab) to their respective predicate functions.n *token-checks*: A dynamic var mapping token block tags (e.g., :nil, :boolean, :symbol) to their respective predicate functions.n* *collection-checks*: A dynamic var mapping collection block tags (e.g., :list, :map, :set, :vector) to their respective predicate functions.

9.2    Functions

9.2.1    boundary?

^{:refer std.block.check/boundary? :added "3.0"}

Returns true if a character is considered a boundary character in Clojure syntax.

boundary? example

(boundary? (first "["))
=> true

(boundary? (first "\""))
=> true

9.2.2    whitespace?

^{:refer std.block.check/whitespace? :added "3.0"}

Returns true if a character is a whitespace character (including spaces, tabs, newlines).

whitespace? example

(whitespace? \space)
=> true

9.2.3    comma?

^{:refer std.block.check/comma? :added "3.0"}

Returns true if a character is a comma.

comma? example

(comma? (first ","))
=> true

9.2.4    linebreak?

^{:refer std.block.check/linebreak? :added "3.0"}

Returns true if a character is a linebreak character.

linebreak? example

(linebreak? \newline)
=> true

9.2.5    delimiter?

^{:refer std.block.check/delimiter? :added "3.0"}

Returns true if a character is a collection delimiter (e.g., (, ), [, ], {, }).

delimiter? example

(delimiter? (first ")"))
=> true

9.2.6    voidspace?

^{:refer std.block.check/voidspace? :added "3.0"}

Determines if an input character represents a "void space" (whitespace or comma).

voidspace? example

(voidspace? \newline)
=> true

9.2.7    linetab?

^{:refer std.block.check/linetab? :added "3.0"}

Checks if a character is a tab character.

linetab? example

(linetab? (first "\t"))
=> true

9.2.8    linespace?

^{:refer std.block.check/linespace? :added "3.0"}

Returns true if a character is a whitespace character that is not a linebreak or a tab.

linespace? example

(linespace? \space)
=> true

9.2.9    voidspace-or-boundary?

^{:refer std.block.check/voidspace-or-boundary? :added "3.0"}

Checks if a character is either a void space or a boundary character.

voidspace-or-boundary? example

(->> (map voidspace-or-boundary? (concat *boundaries*
                                         *linebreaks*))
     (every? true?))
=> true

9.2.10    tag

^{:refer std.block.check/tag :added "3.0"}

Takes a map of checks (predicate functions) and an input, returning the tag (key) of the first predicate that returns true.

tag example

(tag *void-checks* \space)
=> :linespace

(tag *collection-checks* [])
=> :vector

9.2.11    void-tag

^{:refer std.block.check/void-tag :added "3.0"}

Returns the void tag associated with a character (e.g., :linebreak for &#92;newline).

void-tag example

(void-tag \newline)
=> :linebreak

9.2.12    void?

^{:refer std.block.check/void? :added "3.0"}

Determines if a character corresponds to a void block type.

void? example

(void? \newline)
=> true

9.2.13    token-tag

^{:refer std.block.check/token-tag :added "3.0"}

Returns the token tag associated with a Clojure form (e.g., :symbol for a symbol, :boolean for true).

token-tag example

(token-tag 'hello)
=> :symbol

9.2.14    token?

^{:refer std.block.check/token? :added "3.0"}

Determines if a Clojure form is a token type.

token? example

(token? 3/4)
=> true

9.2.15    collection-tag

^{:refer std.block.check/collection-tag :added "3.0"}

Returns the collection tag associated with a Clojure form (e.g., :vector for [], :map for {}).

collection-tag example

(collection-tag [])
=> :vector

9.2.16    collection?

^{:refer std.block.check/collection? :added "3.0"}

Determines if a Clojure form is a collection type.

collection? example

(collection? {})
=> true

9.2.17    comment?

^{:refer std.block.check/comment? :added "3.0"}

Determines if a string is a comment (starts with ;).

comment? example

(comment? "hello")
=> false

(comment? ";hello")
=> true

10    std.block.construct Tutorial

Module: std.block.constructnSource File: src/std/block/construct.cljnTest File: test/std/block/construct_test.clj

The std.block.construct module provides functions for programmatically creating std.block AST nodes. These functions are essential for building block structures from Clojure data, which can then be manipulated, transformed, and eventually rendered back into code strings. It offers constructors for various block types, including void blocks, tokens, comments, and containers.

10.1    Core Concepts

  • *tags*: A dynamic var consolidating tags for different block types (void, token, collection, comment, meta, cons, literal, macro, modifier).n +space+, +newline+, +return+, +formfeed+: Pre-defined std.block instances for common void characters, optimizing their creation.n void-lookup: A map for quickly retrieving pre-defined void blocks.

10.2    Functions

10.2.1    void

^{:refer std.block.construct/void :added "3.0"}

Creates a void block. Void blocks represent non-code elements like spaces, newlines, or comments.

void example

(str (void))
=> "␣"

(str (void \newline))
=> "\n"

10.2.2    space

^{:refer std.block.construct/space :added "3.0"}

Creates a single space block.

space example

(str (space))
=> "␣"

10.2.3    spaces

^{:refer std.block.construct/spaces :added "3.0"}

Creates a sequence of n space blocks.

spaces example

(apply str (spaces 5))
=> "␣␣␣␣␣"

10.2.4    tab

^{:refer std.block.construct/tab :added "3.0"}

Creates a single tab block.

tab example

(str (tab))
=> "\t"

10.2.5    tabs

^{:refer std.block.construct/tabs :added "3.0"}

Creates a sequence of n tab blocks.

tabs example

(apply str (tabs 5))
=> "\t\t\t\t\t"

10.2.6    newline

^{:refer std.block.construct/newline :added "3.0"}

Creates a single newline block.

newline example

(str (newline))
=> "\n"

10.2.7    newlines

^{:refer std.block.construct/newlines :added "3.0"}

Creates a sequence of n newline blocks.

newlines example

(apply str (newlines 5))
=> "\n\n\n\n\n"

10.2.8    comment

^{:refer std.block.construct/comment :added "3.0"}

Creates a comment block from a string. The string must start with ;.

comment example

(str (comment ";hello"))
=> ";hello"

;; Throws exception if string is not a valid comment
(str (comment "hello"))
=> (throws clojure.lang.ExceptionInfo "Not a valid comment string.")

10.2.9    token-dimensions

^{:refer std.block.construct/token-dimensions :added "3.0"}

Returns the [width height] of a token based on its tag and string representation.

token-dimensions example

(token-dimensions :regexp "#\"hello\nworld\"")
=> [6 1]

(token-dimensions :regexp "#\"hello\nworld\n\"")
=> [15 0]

10.2.10    string-token

^{:refer std.block.construct/string-token :added "3.0"}

Constructs a token block specifically for Clojure string literals, including quotes and handling newlines within the string.

string-token example

(str (string-token "hello"))
=> "\"hello\""

(str (string-token "hello\nworld"))
=> "\"hello\\nworld\""

10.2.11    token

^{:refer std.block.construct/token :added "3.0"}

Creates a token block from a Clojure form (symbol, number, keyword, etc.). It automatically determines the correct tag and string representation.

token example

(str (token 'abc))
=> "abc"

(str (token 123))
=> "123"

10.2.12    token-from-string

^{:refer std.block.construct/token-from-string :added "3.0"}

Creates a token block by reading a string input. This is useful for creating tokens from raw text.

token-from-string example

(str (token-from-string "abc"))
=> "abc"

(str (token-from-string "123"))
=> "123"

10.2.13    container-checks

^{:refer std.block.construct/container-checks :added "3.0"}

Performs validation checks for a container block based on its tag, children, and properties. This is an internal helper.

container-checks example

;; No direct test example, but it ensures validity of container construction.
(container-checks :list [(token 1)] {:cons 1})
=> true

10.2.14    container

^{:refer std.block.construct/container :added "3.0"}

Creates a container block (e.g., list, vector, map, set). It takes a tag and a sequence of child blocks.

container example

(str (container :list [(void) (void)]))
=> "(  )"

(str (container :vector [(token 1) (space) (token 2)]))
=> "[1 2]"

10.2.15    uneval

^{:refer std.block.construct/uneval :added "3.0"}

Creates a hash-uneval block (#_), which is a modifier block used to comment out the next form.

uneval example

(str (uneval))
=> "#_"

10.2.16    cursor

^{:refer std.block.construct/cursor :added "3.0"}

Creates a cursor block (|), used for navigation or indicating a position.

cursor example

(str (cursor))
=> "|"

10.2.17    construct-collection

^{:refer std.block.construct/construct-collection :added "3.0"}

A multimethod for constructing collection blocks (:list, :vector, :set, :map) from Clojure data.

construct-collection example

(str (construct-collection [1 2 (void) (void) 3]))
=> "[1 2  3]"

(str (construct-collection '(1 2 3)))
=> "(1 2 3)"

10.2.18    construct-children

^{:refer std.block.construct/construct-children :added "3.0"}

Constructs a sequence of child blocks from a raw Clojure data structure, automatically inserting spaces and handling different element types.

construct-children example

(mapv str (construct-children [1 (newline) (void) 2]))
=> ["1" "\n" "␣" "2"]

10.2.19    block

^{:refer std.block.construct/block :added "3.0"}

The primary entry point for creating any type of std.block from a Clojure data element. It dispatches to token, construct-collection, or returns the element if it's already a block.

block example

(base/block-info (block 1))
=> {:type :token, :tag :long, :string "1", :height 0, :width 1}

(str (block [1 (newline) (void) 2]))
=> "[1\n 2]"

10.2.20    add-child

^{:refer std.block.construct/add-child :added "3.0"}

Adds a child element to an existing container block.

add-child example

(-> (block [])
    (add-child 1)
    (add-child 2)
    (str))
=> "[1 2]"

10.2.21    empty

^{:refer std.block.construct/empty :added "3.0"}

Constructs an empty list block ().

empty example

(str (empty))
=> "()"

10.2.22    root

^{:refer std.block.construct/root :added "3.0"}

Constructs a root block, which is a special container that typically represents the top-level of a parsed file.

root example

(str (root '[a b]))
=> "a b"

10.2.23    contents

^{:refer std.block.construct/contents :added "3.0"}

Reads out the contents of a container block, returning a Clojure data structure.

contents example

(contents (block [1 (space) 2 (space) 3]))
=> '[1 ␣ 2 ␣ 3]

11    std.block.grid Tutorial

Module: std.block.gridnSource File: src/std/block/grid.cljnTest File: test/std/block/grid_test.clj

The std.block.grid module provides advanced functionality for formatting and indenting std.block AST nodes. It's designed to take a raw block structure and apply a set of rules to produce a "gridded" or well-formatted code string, handling line breaks, indentation, and comment placement. This module is crucial for pretty-printing and code generation where consistent formatting is required.

11.1    Core Concepts

  • *bind-length*: A dynamic var controlling the length of binding indentation.n *indent-length*: A dynamic var controlling the base indentation length.n Indentation Rules: The grid function takes a map of rules that define how different forms (e.g., if-let, do, let) should be indented. These rules can specify indent, bind, and scope.n indent: The base indentation level.n bind: The number of binding forms to consider for special indentation.n * scope: A vector or map defining how child scopes should be indented.

11.2    Functions

11.2.1    trim-left

^{:refer std.block.grid/trim-left :added "3.0"}

Removes leading whitespace nodes from a sequence of blocks.

trim-left example

(->> (trim-left [(construct/space)
                 :a
                 (construct/space)])
     (mapv str))
=> [":a" "␣"]

11.2.2    trim-right

^{:refer std.block.grid/trim-right :added "3.0"}

Removes trailing whitespace nodes from a sequence of blocks.

trim-right example

(->> (trim-right [(construct/space)
                  :a
                  (construct/space)])
     (mapv str))
=> ["␣" ":a"]

11.2.3    split-lines

^{:refer std.block.grid/split-lines :added "3.0"}

Splits a sequence of blocks into sub-sequences, where each sub-sequence represents a line, retaining linebreak nodes.

split-lines example

(split-lines [:a :b (construct/newline) :c :d])
=> [[:a :b]
       [(construct/newline) :c :d]]

11.2.4    remove-starting-spaces

^{:refer std.block.grid/remove-starting-spaces :added "3.0"}

Removes redundant spaces at the beginning of lines, especially after linebreaks.

remove-starting-spaces example

(remove-starting-spaces [[(construct/newline)
                          (construct/space)
                          (construct/space) :a :b]
                           [(construct/newline) (construct/space) :c :d]])
=> [[(construct/newline) :a :b]
       [(construct/newline) :c :d]]

11.2.5    adjust-comments

^{:refer std.block.grid/adjust-comments :added "3.0"}

Adds additional newlines after comments to ensure proper formatting and readability.

adjust-comments example

(->> (adjust-comments [(construct/comment ";hello") :a])
     (mapv str))
=> [";hello" "\n" ":a"]

11.2.6    remove-extra-linebreaks

^{:refer std.block.grid/remove-extra-linebreaks :added "3.0"}

Removes redundant or excessive linebreak nodes from a sequence of lines.

remove-extra-linebreaks example

(remove-extra-linebreaks [[:a]
                            [(construct/newline)]
                            [(construct/newline)]
                            [(construct/newline)]
                            [:b]])
=> [[:a]
       [(construct/newline)]
       [:b]]

11.2.7    grid-scope

^{:refer std.block.grid/grid-scope :added "3.0"}

Calculates the grid scope for child nodes based on the parent scope. This is an internal helper for indentation logic.

grid-scope example

(grid-scope [{0 1} 1])
=> [{0 1} 0]

11.2.8    grid-rules

^{:refer std.block.grid/grid-rules :added "3.0"}

Creates indentation rules for the current block based on its tag, symbol, parent scope, and a global rules map.

grid-rules example

(grid-rules :list nil nil nil)
=> {:indent 0, :bind 0, :scope []}

(grid-rules :vector nil nil nil)
=> {:indent 0, :bind 0, :scope [0]}

(grid-rules :list 'add [1] nil)
=> {:indent 1, :bind 0, :scope [0]}

(grid-rules :list 'if nil '{if {:indent 1}})
=> {:indent 1, :bind 0, :scope []}

11.2.9    indent-bind

^{:refer std.block.grid/indent-bind :added "3.0"}

Returns the number of lines to indent for binding forms within a block, based on the bind rule.

indent-bind example

(indent-bind [[(construct/token 'if-let)]
              [(construct/newline)]
              [(construct/newline) (construct/block '[i (pos? 0)])]
              [(construct/newline) (construct/block '(+ i 1))]]
             1)
=> 2

(indent-bind [[(construct/token 'if-let)]
              [(construct/newline)]
              [(construct/newline) (construct/block '[i (pos? 0)])]
              [(construct/newline) (construct/block '(+ i 1))]]
             0)
=> 0

11.2.10    indent-lines

^{:refer std.block.grid/indent-lines :added "3.0"}

Indents a sequence of lines based on a given anchor and indentation rule.

indent-lines example

(-> (indent-lines [[(construct/token 'if-let)]
                   [(construct/newline)]
                   [(construct/newline) (construct/block '[i (pos? 0)])]
                   [(construct/newline) (construct/block '(+ i 1))]]
                  1
                  {:indent 1
                   :bind 1})
    (construct/contents))
=> '([if-let]
        (\n ␣ ␣ ␣ ␣)
        (\n ␣ ␣ ␣ ␣ [i (pos? 0)])
        (\n ␣ ␣ (+ i 1)))

11.2.11    grid

^{:refer std.block.grid/grid :added "3.0"}

The main function for formatting a container block. It applies indentation rules and scope to produce a well-formatted block structure.

grid example

(-> (construct/block ^:list ['if-let
                             (construct/newline)
                             (construct/newline) (construct/block '[i (pos? 0)])
                             (construct/newline) (construct/block '(+ i 1))])
    (grid 1 {:rules {'if-let {:indent 1
                              :bind 1}}})
    (construct/contents))
=> '(if-let
        \n ␣ ␣ ␣ ␣ ␣
        \n ␣ ␣ ␣ ␣ ␣ [i (pos? 0)]
        \n ␣ ␣ (+ i 1))

12    std.block.parse Tutorial

Module: std.block.parsenSource File: src/std/block/parse.cljnTest File: test/std/block/parse_test.clj

The std.block.parse module is responsible for parsing Clojure code strings into std.block AST nodes. It acts as the core parser, dispatching to various parsing methods based on the initial character of a form. This module leverages std.block.reader for character-level input and std.block.construct for building the AST nodes.

12.1    Core Concepts

  • *end-delimiter*: A dynamic var used to track the expected closing delimiter during parsing of collections.n *symbol-allowed*: A dynamic var defining characters allowed within symbols.n *dispatch-options*: A map that dispatches parsing logic based on the first character encountered (e.g., ( for lists, # for hash forms).n -parse multimethod: The central parsing function, extended for different dispatch keys (e.g., :void, :token, :list, :hash).n *hash-options* and *hash-dispatch*: Maps defining how different hash-prefixed forms (e.g., #{, #_, #?) are parsed.

12.2    Functions

12.2.1    read-dispatch

^{:refer std.block.parse/read-dispatch :added "3.0"}

Dispatches parsing logic based on the first character of a form. It returns a keyword indicating the type of form to be parsed.

read-dispatch example

(read-dispatch (char 9))
=> :void

(read-dispatch (first "#"))
=> :hash

12.2.2    -parse

^{:refer std.block.parse/-parse :added "3.0"}

The extendable parsing multimethod. It takes a reader and returns a std.block AST node. This function is the core of the parsing process.

-parse example

(select-keys (base/block-info (-parse (reader/create ":a")))
             [:type :tag :string])
=> {:type :token, :tag :keyword, :string ":a"}

12.2.3    parse-void

^{:refer std.block.parse/parse-void :added "3.0"}

Reads a void block (e.g., space, newline, tab) from the reader.

parse-void example

(->> (reader/read-repeatedly (reader/create " \t\n\f")
                             parse-void
                             eof-block?)
     (take 5)
     (map str))
=> ["\u202F" "\t" "\n" "\f"]

12.2.4    parse-comment

^{:refer std.block.parse/parse-comment :added "3.0"}

Reads a comment block from the reader.

parse-comment example

(-> (reader/create ";this is a comment")
    parse-comment
    (base/block-info))
=> {:type :comment, :tag :comment, :string ";this is a comment", :height 0, :width 18}

12.2.5    parse-token

^{:refer std.block.parse/parse-token :added "3.0"}

Reads a token block (e.g., symbol, number, string) from the reader.

parse-token example

(-> (reader/create "abc")
    (parse-token)
    (base/block-value))
=> 'abc

(-> (reader/create "3/5")
    (parse-token)
    (base/block-value))
=> 3/5

12.2.6    parse-keyword

^{:refer std.block.parse/parse-keyword :added "3.0"}

Reads a keyword block from the reader, handling both simple and namespaced keywords.

parse-keyword example

(-> (reader/create ":a/b")
    (parse-keyword)
    (base/block-value))
=> :a/b

(-> (reader/create "::hello")
    (parse-keyword)
    (base/block-value))
=> (keyword ":hello")

12.2.7    parse-reader

^{:refer std.block.parse/parse-reader :added "3.0"}

Reads a character literal (e.g., &#92;c) from the reader.

parse-reader example

(-> (reader/create "\\c")
    (parse-reader)
    (base/block-info))
=> (contains {:type :token, :tag :char, :string "\\c"})

12.2.8    read-string-data

^{:refer std.block.parse/read-string-data :added "3.0"}

Reads the content of a string literal from the reader, handling escape sequences and newlines.

read-string-data example

(read-string-data (reader/create "\"hello\""))
=> "hello"

12.2.9    eof-block?

^{:refer std.block.parse/eof-block? :added "3.0"}

Checks if a block represents the end-of-file.

eof-block? example

(eof-block? (-parse (reader/create "")))
=> true

12.2.10    delimiter-block?

^{:refer std.block.parse/delimiter-block? :added "3.0"}

Checks if a block represents a closing delimiter.

delimiter-block? example

(delimiter-block?
 (binding [*end-delimiter* (first ")")]
   (-parse (reader/create ")"))))
=> true

12.2.11    read-whitespace

^{:refer std.block.parse/read-whitespace :added "3.0"}

Reads a sequence of whitespace characters from the reader and returns them as a vector of void blocks.

read-whitespace example

(count (read-whitespace (reader/create "   ")))
=> 3

12.2.12    parse-non-expressions

^{:refer std.block.parse/parse-non-expressions :added "3.0"}

Parses whitespace and non-expression blocks until the next expression block is found.

parse-non-expressions example

(str (parse-non-expressions (reader/create " \na")))
=> "[(\u202F \n) a]"

12.2.13    read-start

^{:refer std.block.parse/read-start :added "3.0"}

Helper function to consume and verify starting characters of a form (e.g., ( for a list, ~@ for unquote-splicing).

read-start example

(read-start (reader/create "~@") "~#")
=> (throws)

12.2.14    read-collection

^{:refer std.block.parse/read-collection :added "3.0"}

Reads all child blocks within a collection, respecting the start and end delimiters.

read-collection example

(->> (read-collection (reader/create "(1 2 3 4 5)") "(" (first ")"))
     (apply str))
=> "1\u202F2\u202F3\u202F4\u202F5"

12.2.15    read-cons

^{:refer std.block.parse/read-cons :added "3.0"}

Helper method for reading "cons" forms (e.g., @x, 'x, ^x).

read-cons example

(->> (read-cons (reader/create "@hello") "@")
     (map base/block-string))
=> '("hello")

(->> (read-cons (reader/create "^hello {}") "^" 2)
     (map base/block-string))
=> '("hello" " " "{}")

12.2.16    parse-collection

^{:refer std.block.parse/parse-collection :added "3.0"}

Parses a collection block (list, vector, map, set, fn, root) from the reader.

parse-collection example

(-> (parse-collection (reader/create "#(+ 1 2 3 4)") :fn)
    (base/block-value))
=> '(fn* [] (+ 1 2 3 4))

(-> (parse-collection (reader/create "(1 2 3 4)") :list)
    (base/block-value))
=> '(1 2 3 4)

(-> (parse-collection (reader/create "[1 2 3 4]") :vector)
    (base/block-value))
=> [1 2 3 4]

(-> (parse-collection (reader/create "{1 2 3 4}") :map)
    (base/block-value))
=> {1 2, 3 4}

(-> (parse-collection (reader/create "#{1 2 3 4}") :set)
    (base/block-value))
=> #{1 4 3 2}

12.2.17    parse-cons

^{:refer std.block.parse/parse-cons :added "3.0"}

Parses a "cons" block (deref, meta, quote, syntax, unquote, unquote-splice, select, select-splice, var, hash-keyword, hash-meta, hash-eval).

parse-cons example

(-> (parse-cons (reader/create "~hello") :unquote)
    (base/block-value))
=> '(unquote hello)

(-> (parse-cons (reader/create "~@hello") :unquote-splice)
    (base/block-value))
=> '(unquote-splicing hello)

(-> (parse-cons (reader/create "^tag {:a 1}") :meta)
    (base/block-value)
    ((juxt meta identity)))
=> [{:tag 'tag} {:a 1}]

(-> (parse-cons (reader/create "@hello") :deref)
    (base/block-value))
=> '(deref hello)

(-> (parse-cons (reader/create "`hello") :syntax)
    (base/block-value))
=> '(quote std.block.parse-test/hello)

12.2.18    parse-unquote

^{:refer std.block.parse/parse-unquote :added "3.0"}

Parses a block starting with ~ (unquote or unquote-splice).

parse-unquote example

(-> (parse-unquote (reader/create "~hello"))
    (base/block-value))
=> '(unquote hello)

(-> (parse-unquote (reader/create "~@hello"))
    (base/block-value))
=> '(unquote-splicing hello)

12.2.19    parse-select

^{:refer std.block.parse/parse-select :added "3.0"}

Parses a block starting with #? (reader conditional or reader conditional splicing).

parse-select example

(-> (parse-select (reader/create "#?(:cljs a)"))
    (base/block-value))
=> '(? {:cljs a})

(-> (parse-select (reader/create "#?@(:cljs a)"))
    (base/block-value))
=> '(?-splicing {:cljs a})

12.2.20    parse-hash-uneval

^{:refer std.block.parse/parse-hash-uneval :added "3.0"}

Parses a hash-uneval block (#_).

parse-hash-uneval example

(str (parse-hash-uneval (reader/create "#_")))
=> "#_"

12.2.21    parse-hash-cursor

^{:refer std.block.parse/parse-hash-cursor :added "3.0"}

Parses a hash-cursor block (#|).

parse-hash-cursor example

(str (parse-hash-cursor (reader/create "#|")))
=> "|"

12.2.22    parse-hash

^{:refer std.block.parse/parse-hash :added "3.0"}

Parses a block starting with # (hash forms like sets, fn literals, regex, metadata, etc.).

parse-hash example

(-> (parse-hash (reader/create "#{1 2 3}"))
    (base/block-value))
=> #{1 2 3}

(-> (parse-hash (reader/create "#(+ 1 2)"))
    (base/block-value))
=> '(fn* [] (+ 1 2))

(-> (parse-hash (reader/create "#\"hello\""))
    (base/block-value))
=> #"hello"

(-> (parse-hash (reader/create "#^hello {}"))
    (base/block-value))
=> (with-meta {} {:tag 'hello})

(-> (parse-hash (reader/create "#\'hello"))
    (base/block-value))
=> '(var hello)

(-> (parse-hash (reader/create "#=(list 1 2 3)"))
    (base/block-value))
=> '(1 2 3)

(-> (parse-hash (reader/create "#?(:clj true)"))
    (base/block-value))
=> '(? {:clj true})

(-> (parse-hash (reader/create "#?@(:clj [1 2 3])"))
    (base/block-value))
=> '(?-splicing {:clj [1 2 3]})

(-> (parse-hash (reader/create "#:hello {:a 1 :b 2}"))
    (base/block-value))
=> #:hello{:b 2, :a 1}

(-> (parse-hash (reader/create "#inst \"2018-08-06T06:01:40.682-00:00\""))
    (base/block-value))
=> #inst "2018-08-06T06:01:40.682-00:00"

12.2.23    parse-string

^{:refer std.block.parse/parse-string :added "3.0"}

Parses a single block from a string input. This is a convenient entry point for parsing.

parse-string example

(-> (parse-string "#(:b {:b 1})")
    (base/block-value))
=> '(fn* [] ((keyword "b") {(keyword "b") 1}))

12.2.24    parse-root

^{:refer std.block.parse/parse-root :added "3.0"}

Parses a string into a root block, which can contain multiple top-level forms.

parse-root example

(str (parse-root "a b c"))
=> "a b c"

13    std.block.reader Tutorial

Module: std.block.readernSource File: src/std/block/reader.cljnTest File: test/std/block/reader_test.clj

The std.block.reader module provides a set of low-level functions for character-by-character reading and manipulation of input streams, specifically designed for parsing Clojure code. It wraps clojure.tools.reader.reader-types to offer a more convenient and block-oriented interface for parsing.

13.1    Core Concepts

  • Reader Abstraction: Provides functions to create, step through, peek at, and unread characters from an input string, mimicking a traditional stream reader.n* Position Tracking: Integrates with clojure.tools.reader's indexing reader to track line and column numbers.

13.2    Functions

13.2.1    create

^{:refer std.block.reader/create :added "3.0"}

Creates an IndexingPushbackReader from a string, suitable for character-by-character reading.

create example

(type (create "hello world"))
=> clojure.tools.reader.reader_types.IndexingPushbackReader

13.2.2    reader-position

^{:refer std.block.reader/reader-position :added "3.0"}

Returns the current [line column] position of the reader.

reader-position example

(-> (create "abc")
    step-char
    step-char
    reader-position)
=> [1 3]

13.2.3    throw-reader

^{:refer std.block.reader/throw-reader :added "3.0"}

Throws an ExceptionInfo with a message and the current reader position, useful for reporting parsing errors.

throw-reader example

(throw-reader (create "abc")
              "Message"
              {:data true})
=> (throws)

13.2.4    step-char

^{:refer std.block.reader/step-char :added "3.0"}

Moves the reader one character forward and returns the reader itself.

step-char example

(-> (create "abc")
    step-char
    read-char
    str)
=> "b"

13.2.5    read-char

^{:refer std.block.reader/read-char :added "3.0"}

Reads a single character from the reader and advances its position.

read-char example

(->> read-char
     (read-repeatedly (create "abc"))
     (take 3)
     (apply str))
=> "abc"

13.2.6    ignore-char

^{:refer std.block.reader/ignore-char :added "3.0"}

Reads a single character, ignores it (returns nil), and advances the reader's position.

ignore-char example

(->> ignore-char
     (read-repeatedly (create "abc"))
     (take 3)
     (apply str))
=> ""

13.2.7    unread-char

^{:refer std.block.reader/unread-char :added "3.0"}

Pushes a character back onto the reader, effectively moving the reader's position backward.

unread-char example

(-> (create "abc")
    (step-char)
    (unread-char \A)
    (reader/slurp))
=> "Abc"

13.2.8    peek-char

^{:refer std.block.reader/peek-char :added "3.0"}

Returns the next character in the stream without advancing the reader's position.

peek-char example

(->> (read-times (create "abc")
                 peek-char
                 3)
     (apply str))
=> "aaa"

13.2.9    read-while

^{:refer std.block.reader/read-while :added "3.0"}

Reads characters from the reader as long as a given predicate remains true.

read-while example

(read-while (create "abcde")
            (fn [ch]
              (not= (str ch) "d")))
=> "abc"

13.2.10    read-until

^{:refer std.block.reader/read-until :added "3.0"}

Reads characters from the reader until a given predicate becomes true.

read-until example

(read-until (create "abcde")
            (fn [ch]
              (= (str ch) "d")))
=> "abc"

13.2.11    read-times

^{:refer std.block.reader/read-times :added "3.0"}

Reads input a specified number of times using a provided reading function.

read-times example

(->> (read-times (create "abcdefg")
                 #(str (read-char %) (read-char %))
                 2))
=> ["ab" "cd"]

13.2.12    read-repeatedly

^{:refer std.block.reader/read-repeatedly :added "3.0"}

Reads input repeatedly until a stop predicate is met.

read-repeatedly example

(->> (read-repeatedly (create "abcdefg")
                      #(str (read-char %) (read-char %))
                      empty?)
     (take 5))
=> ["ab" "cd" "ef" "g"]

13.2.13    read-include

^{:refer std.block.reader/read-include :added "3.0"}

Reads characters, including those that satisfy a predicate, and returns them along with the first character that doesn't satisfy the predicate.

read-include example

(read-include (create "  a")
              read-char (complement check/voidspace?))
=> [[" " " "] "a"]

13.2.14    slurp

^{:refer std.block.reader/slurp :added "3.0"}

Reads the rest of the input from the reader until EOF.

slurp example

(reader/slurp (reader/step-char (create "abc efg")))
=> "bc efg"

13.2.15    read-to-boundary

^{:refer std.block.reader/read-to-boundary :added "3.0"}

Reads characters until a boundary character or a character not allowed by allowed is encountered.

read-to-boundary example

(read-to-boundary (create "abc efg"))
=> "abc"

14    std.block: A Comprehensive Summary (including submodules)

The std.block module is a foundational component of the foundation-base ecosystem, providing an Abstract Syntax Tree (AST) or "code-as-data" abstraction for representing Clojure code. It allows for the parsing, manipulation, and structured representation of code as a hierarchical collection of "blocks." This module is critical for metaprogramming, code analysis, and especially for transpilation processes where code needs to be understood and transformed systematically.

14.1    std.block (Main Namespace)

This namespace orchestrates the functionality of its submodules, providing a unified interface for working with code blocks. It re-exports key functions from its sub-namespaces, making it a convenient entry point for block manipulation.

Key Re-exported Functions:

  • From std.block.base: block?, expression?, type, tag, string, length, width, height, prefixed, suffixed, verify, value, value-string, children, info.n From std.block.construct: block, void, space, spaces, newline, newlines, tab, tabs, comment, uneval, cursor, contents, container, root.n From std.block.parse: parse-string, parse-root.n* From std.block.type: void?, space?, linebreak?, linespace?, eof?, comment?, token?, container?, modifier?.

14.2    std.block.base (Core Block Definitions and Protocols)

This sub-namespace defines the fundamental protocols and basic operations that all code blocks adhere to. It establishes the common interface for querying block properties.

Core Concepts:

  • IBlock Protocol: The base protocol for all code blocks, defining methods like _type, _tag, _string, _length, _width, _height, _prefixed, _suffixed, _verify.n IBlockExpression Protocol: For blocks that have an associated Clojure value, defining _value and _value_string.n IBlockModifier Protocol: For blocks that modify an accumulator (e.g., #_ for unevaluated forms), defining _modify.n IBlockContainer Protocol: For blocks that contain other blocks (e.g., lists, vectors), defining _children and _replace_children.n Block Types and Tags: Blocks are categorized by :type (e.g., :void, :token, :comment, :collection, :modifier) and more specific :tag (e.g., :eof, :symbol, :list, :meta, :hash-uneval).n* *container-limits*: Defines the start and end delimiters and properties for various container types (e.g., (, ), [, ], {, }).

Key Functions:

  • block?: Checks if an object is an IBlock.n block-* functions: Accessors for block properties (block-type, block-tag, block-string, block-length, block-width, block-height, block-prefixed, block-suffixed, block-verify).n expression?: Checks if a block has a value.n block-value: Returns the Clojure value of an expression block.n block-value-string: Returns the pr-str representation of the block's value.n modifier?: Checks if a block is a modifier.n block-modify: Applies a modifier's logic.n container?: Checks if a block is a container.n block-children: Returns the child blocks of a container.n replace-children: Replaces the children of a container.n block-info: Returns a map of common block information.

14.3    std.block.check (Character and Token Classification)

This sub-namespace provides utilities for classifying characters and Clojure forms, which is essential for the parsing process.

Core Concepts:

  • Character Properties: Functions to determine if a character is a boundary, whitespace, comma, linebreak, or delimiter.n* Form Tags: Functions to categorize Clojure forms as void, token, or collection types.

Key Functions:

  • boundary?, whitespace?, comma?, linebreak?, delimiter?, voidspace?, linetab?, linespace?, voidspace-or-boundary?: Predicates for character classification.n tag: Generic function to find a tag based on a set of checks.n void-tag, void?: Classifies and checks for void forms (whitespace, newlines).n token-tag, token?: Classifies and checks for token forms (numbers, symbols, keywords, strings).n collection-tag, collection?: Classifies and checks for collection forms (lists, vectors, maps, sets).n* comment?: Checks if a string is a comment.

14.4    std.block.construct (Block Construction)

This sub-namespace provides functions for programmatically creating various types of code blocks.

Core Concepts:

  • Direct Block Creation: Functions to create instances of VoidBlock, CommentBlock, TokenBlock, and ContainerBlock.n* Special Blocks: uneval (for #_) and cursor (for |) as modifier blocks.

Key Functions:

  • void, space, spaces, tab, tabs, newline, newlines: Create void blocks (whitespace, newlines, tabs).n comment: Creates a comment block.n string-token, token, token-from-string: Create token blocks for various literal types.n container: Creates a container block (list, vector, map, set, root), handling delimiters and children.n uneval: Creates a modifier block for #_ (unevaluated forms).n cursor: Creates a modifier block for | (used as a cursor in editing contexts).n construct-collection (multimethod): A multimethod for constructing blocks from Clojure collections (lists, vectors, maps, sets).n construct-children: Helper to convert a sequence of Clojure forms into a sequence of blocks, inserting correct spacing.n block: A generic function to construct a block from a Clojure form (token or collection).n add-child: Adds a child block to a container block.n empty: Creates an empty list container block.n root: Creates a special container block representing the root of a code structure.n contents: Extracts the value representation of a container block's children, ignoring void blocks.

14.5    std.block.grid (Code Formatting and Layout)

This sub-namespace provides algorithms for formatting and arranging code blocks into a readable grid-like structure, handling indentation and line breaks.

Core Concepts:

  • Line-based Formatting: Operations for splitting blocks into lines, removing extra spaces/linebreaks, and adjusting comments.n Indentation Rules: grid-rules defines how different container types and symbols should be indented.n Scope-aware Indentation: Indentation can be adjusted based on the nesting level and specific rules for forms like let or if.

Key Functions:

  • trim-left, trim-right: Remove void blocks from the ends of sequences.n split-lines: Splits a sequence of blocks into a sequence of lines based on linebreak blocks.n remove-starting-spaces: Removes leading space blocks from lines.n adjust-comments: Ensures comments are followed by newlines.n remove-extra-linebreaks: Collapses multiple consecutive linebreak blocks into a single one.n grid-scope: Calculates indentation scope for child nodes.n grid-rules: Determines indentation and binding rules for a given block type and symbol.n indent-bind: Calculates the number of lines to bind for indentation.n indent-lines: Applies indentation to a sequence of lines based on rules.n* grid: The main function for formatting a container block into a grid, using the defined rules for indentation and line breaks.

14.6    std.block.parse (Code Parsing)

This sub-namespace defines the core logic for parsing raw input strings into a tree of std.block objects. It leverages clojure.tools.reader for low-level character reading.

Core Concepts:

  • tools.reader Integration: Uses clojure.tools.reader.reader-types/IndexingPushbackReader for character-by-character input processing, allowing for peeking, reading, and unreading characters.n Dispatch Mechanism: Uses read-dispatch and a multimethod -parse to determine the type of block to parse based on the leading character.n Recursive Descent Parsing: Parses complex structures like collections and forms recursively.n* Error Handling: Throws informative exceptions for parsing errors (e.g., unmatched delimiters, unexpected EOF).

Key Functions:

  • read-dispatch: Determines the block type (:void, :hash, :list, :token, etc.) based on the next character.n -parse (multimethod): The main dispatch function for parsing different block types.n parse-void: Parses whitespace, newlines, commas.n parse-comment: Parses a comment line.n parse-token: Parses numbers, symbols, booleans, ratios.n parse-keyword: Parses keywords (:key, ::key).n parse-reader: Parses reader forms (e.g., &#92;c).n read-string-data: Parses string literals, handling newlines and escaped characters.n parse-collection: Parses collections ((), [], {}, #{}).n parse-cons: Parses forms with prefixes (e.g., ', ~, @, #).n parse-unquote, parse-select, parse-hash-uneval, parse-hash-cursor, parse-hash: Specific parsers for reader macros and prefixed forms.n parse-string: Parses an entire string into a single block.n parse-root: Parses a string into a root block, representing the entire input structure.n eof-block?, delimiter-block?: Check for EOF or delimiter blocks.n read-whitespace: Reads a sequence of whitespace blocks.n parse-non-expressions: Parses blocks up to the next expression.n read-start: Verifies starting delimiters for collections/forms.n read-collection: Reads all child blocks within a collection, respecting delimiters.n read-cons: Reads child blocks for cons-like forms (e.g., 'x, @y).

14.7    std.block.reader (Low-Level Character Reading)

This sub-namespace provides an enhanced character-level reader built on top of clojure.tools.reader, offering utility functions for detailed input stream manipulation.

Core Concepts:

  • IndexingPushbackReader: Leverages clojure.tools.reader.reader-types/IndexingPushbackReader for precise control over the input stream, including tracking line and column numbers.n Character-level Operations: Functions for peeking, reading, unreading, and skipping characters.n Predicate-based Reading: Functions to read characters until a predicate is met or while a predicate is true.

Key Functions:

  • create: Creates an IndexingPushbackReader from a string.n reader-position: Returns the current line and column of the reader.n throw-reader: Throws an exception with detailed position information.n step-char: Reads a character and advances the reader.n read-char: Reads the next character.n ignore-char: Skips the next character.n unread-char: Pushes a character back onto the reader.n peek-char: Returns the next character without advancing the reader.n read-while: Reads characters as long as a predicate is true.n read-until: Reads characters until a predicate is true.n read-times: Reads characters a specified number of times.n read-repeatedly: Reads characters repeatedly until a stop condition.n read-include: Reads characters while accumulating non-expression blocks.n slurp: Reads the rest of the reader's input as a string.n read-to-boundary: Reads characters until a boundary character is encountered.

14.8    std.block.type (Concrete Block Implementations)

This sub-namespace defines the concrete deftype implementations for the various block types, adhering to the std.protocol.block protocols.

Core Concepts:

  • VoidBlock: Represents whitespace, newlines, tabs, and EOF.n CommentBlock: Represents comments.n TokenBlock: Represents atomic values like numbers, symbols, keywords, strings.n ContainerBlock: Represents collections (lists, vectors, maps, sets, root) and forms with prefixes (e.g., 'x).n ModifierBlock: Represents reader macro modifiers (e.g., #_, |).n* Dimensions: Tracks width and height of blocks for formatting.

Key Functions:

  • block-compare: A utility for comparing two blocks.n void-block?, void-block: Checks for and constructs VoidBlock.n space-block?, linebreak-block?, linespace-block?, eof-block?, nil-void?: Specific predicates for types of void blocks.n comment-block?, comment-block: Checks for and constructs CommentBlock.n token-block?, token-block: Checks for and constructs TokenBlock.n container-width, container-height: Calculates dimensions for ContainerBlock.n container-string (multimethod): Generates the string representation for different container types.n container-value-string: Generates a string representation of the value of a container block (used for block-value-string).n container-block?, container-block: Checks for and constructs ContainerBlock.n* modifier-block?, modifier-block: Checks for and constructs ModifierBlock.

14.9    std.block.value (Extracting Values from Blocks)

This sub-namespace focuses on extracting the underlying Clojure value from code blocks, applying any modifiers in the process.

Core Concepts:

  • Value Extraction: Functions to convert a block representation back into a runnable Clojure value.n* Modifier Application: Correctly applies the logic of modifier blocks (like #_) during value extraction.

Key Functions:

  • apply-modifiers: Applies IBlockModifier logic to a sequence of blocks.n child-values: Extracts the Clojure values of child blocks within a container, applying modifiers.n root-value, list-value, map-value, set-value, vector-value: Extract values from container blocks.n deref-value, meta-value, quote-value, var-value, hash-keyword-value, select-value, select-splice-value, unquote-value, unquote-splice-value: Extract values from various prefixed forms.n from-value-string: Uses read-string on the block-value-string to get the value.n* *container-values*: A dynamic map mapping container tags to the functions that extract their Clojure values.

14.10    Usage Pattern:

The std.block module and its sub-namespaces provide the backbone for:

  • Transpilers and Compilers: Representing and manipulating source code during the conversion to other languages.n Code Editors and IDEs: Providing structured editing, syntax highlighting, and formatting capabilities.n Static Analysis Tools: Analyzing code structure and properties.n* Metaprogramming and Code Generation: Programmatically constructing and transforming Clojure code.

By offering a powerful and finely-grained abstraction over Clojure code structure, std.block enables sophisticated processing and transformation of code within the foundation-base ecosystem.

15    std.block.type Tutorial

Module: std.block.typenSource File: src/std/block/type.cljnTest File: test/std/block/type_test.clj

The std.block.type module defines the various concrete implementations of std.block AST nodes (VoidBlock, CommentBlock, TokenBlock, ContainerBlock, ModifierBlock). It also provides predicate functions to check the type of a block and functions to construct new instances of these block types directly. This module is crucial for understanding the internal representation of blocks and for low-level block manipulation.

15.1    Core Concepts

  • *tab-width*: A dynamic var controlling the assumed width of a tab character for layout calculations.n VoidBlock: Represents non-code elements like spaces, newlines, tabs, commas, or EOF signals.n CommentBlock: Represents single-line comments starting with ;.n TokenBlock: Represents literal values, symbols, keywords, numbers, strings, etc.n ContainerBlock: Represents collections like lists, vectors, maps, and sets, holding other blocks as children.n* ModifierBlock: Represents special forms that modify subsequent forms, like #_ (uneval) or | (cursor).

15.2    Functions

15.2.1    block-compare

^{:refer std.block.type/block-compare :added "3.0"}

Compares two blocks for equality based on their tag and string representation. Returns 0 if equal, a negative number if the first is "less" than the second, and a positive number otherwise. This is used for Comparable implementation.

block-compare example

(block-compare (construct/void \space)
                 (construct/void \space))
=> 0

15.2.2    void-block?

^{:refer std.block.type/void-block? :added "3.0"}

Checks if a block is a VoidBlock instance.

void-block? example

(void-block? (construct/void))
=> true

15.2.3    void-block

^{:refer std.block.type/void-block :added "3.0"}

Constructs a new VoidBlock instance directly.

void-block example

(-> (void-block :linespace \tab 1 0)
    (base/block-info))
=> {:type :void, :tag :linespace, :string "\t", :height 0, :width 1}

15.2.4    space-block?

^{:refer std.block.type/space-block? :added "3.0"}

Checks if a block represents a space character.

space-block? example

(space-block? (construct/space))
=> true

15.2.5    linebreak-block?

^{:refer std.block.type/linebreak-block? :added "3.0"}

Checks if a block represents a linebreak character.

linebreak-block? example

(linebreak-block? (construct/newline))
=> true

15.2.6    linespace-block?

^{:refer std.block.type/linespace-block? :added "3.0"}

Checks if a block represents a non-linebreak whitespace character (e.g., &#92;space, &#92;tab).

linespace-block? example

(linespace-block? (construct/space))
=> true

15.2.7    eof-block?

^{:refer std.block.type/eof-block? :added "3.0"}

Checks if a block represents the end-of-file signal.

eof-block? example

(eof-block? (construct/void nil))
=> true

15.2.8    nil-void?

^{:refer std.block.type/nil-void? :added "3.0"}

Checks if a block is nil or a VoidBlock.

nil-void? example

(nil-void? nil)
=> true

(nil-void? (construct/block nil))
=> false

(nil-void? (construct/space))
=> true

15.2.9    comment-block?

^{:refer std.block.type/comment-block? :added "3.0"}

Checks if a block is a CommentBlock instance.

comment-block? example

(comment-block? (construct/comment ";;hello"))
=> true

15.2.10    comment-block

^{:refer std.block.type/comment-block :added "3.0"}

Constructs a new CommentBlock instance directly.

comment-block example

(-> (comment-block ";hello")
    (base/block-info))
=> {:type :comment, :tag :comment, :string ";hello", :height 0, :width 6}

15.2.11    token-block?

^{:refer std.block.type/token-block? :added "3.0"}

Checks if a block is a TokenBlock instance.

token-block? example

(token-block? (construct/token "hello"))
=> true

15.2.12    token-block

^{:refer std.block.type/token-block :added "3.0"}

Constructs a new TokenBlock instance directly.

token-block example

(base/block-info (token-block :symbol "abc" 'abc "abc" 3 0))
=> {:type :token, :tag :symbol, :string "abc", :height 0, :width 3}

15.2.13    container-width

^{:refer std.block.type/container-width :added "3.0"}

Calculates the visual width of a container block, considering its children and delimiters.

container-width example

(container-width (construct/block [1 2 3 4]))
=> 9

15.2.14    container-height

^{:refer std.block.type/container-height :added "3.0"}

Calculates the height (number of lines) of a container block.

container-height example

(container-height (construct/block [(construct/newline)
                                      (construct/newline)]))
=> 2

15.2.15    container-string

^{:refer std.block.type/container-string :added "3.0"}

Returns the string representation of a container block, including its delimiters and children's string representations. This is a multimethod extending base/block-tag.

container-string example

(container-string (construct/block [1 2 3]))
=> "[1 2 3]"

15.2.16    container-value-string

^{:refer std.block.type/container-value-string :added "3.0"}

Returns the string representation used to generate the value of a container block, often used for debugging or internal representation.

container-value-string example

(container-value-string (construct/block [::a :b :c]))
=> "[:std.block.type-test/a :b :c]"

(container-value-string (parse/parse-string "[::a :b :c]"))
=> "[(keyword ":a") (keyword "b") (keyword "c")]"

15.2.17    container-block?

^{:refer std.block.type/container-block? :added "3.0"}

Checks if a block is a ContainerBlock instance.

container-block? example

(container-block? (construct/block []))
=> true

15.2.18    container-block

^{:refer std.block.type/container-block :added "3.0"}

Constructs a new ContainerBlock instance directly.

container-block example

(-> (container-block :fn [(construct/token '+)
                            (construct/void)
                            (construct/token '1)]
                       (construct/*container-props* :fn))
    (base/block-value))
=> '(fn* [] (+ 1))

15.2.19    modifier-block?

^{:refer std.block.type/modifier-block? :added "3.0"}

Checks if a block is a ModifierBlock instance.

modifier-block? example

(modifier-block? (construct/uneval))
=> true

15.2.20    modifier-block

^{:refer std.block.type/modifier-block :added "3.0"}

Constructs a new ModifierBlock instance directly.

modifier-block example

(select-keys (modifier-block :hash-uneval "#_" (fn [acc _] acc))
             [:tag :string])
=> {:tag :hash-uneval, :string "#_"}

16    std.block.value Tutorial

Module: std.block.valuenSource File: src/std/block/value.cljnTest File: test/std/block/value_test.clj

The std.block.value module is responsible for extracting the actual Clojure values from std.block AST nodes. It provides functions to convert various block types (tokens, collections, special forms) back into their corresponding Clojure data structures, handling modifiers like #_ (uneval) in the process. This module is crucial for bridging the gap between the AST representation and executable Clojure code.

16.1    Core Concepts

  • Value Extraction: The primary goal is to get the Clojure data represented by a block.n Modifier Application: Correctly applies the logic of modifier blocks (like #_) during value extraction.n *container-values*: A dynamic var mapping container tags to functions that extract their Clojure value.

16.2    Functions

16.2.1    apply-modifiers

^{:refer std.block.value/apply-modifiers :added "3.0"}

Applies modifier blocks within a sequence of blocks to an accumulator. For example, #_ modifiers will remove the subsequent block from the sequence.

apply-modifiers example

(apply-modifiers [(construct/uneval)
                  (construct/uneval)
                  1 2 3])
=> [3]

16.2.2    child-values

^{:refer std.block.value/child-values :added "3.0"}

Returns the Clojure values of the children within a container block, applying any modifiers present.

child-values example

(child-values (parse/parse-string "[1 #_2 3]"))
=> [1 3]

16.2.3    root-value

^{:refer std.block.value/root-value :added "3.0"}

Returns the Clojure value of a :root block, typically as a (do ...) form if it contains multiple top-level expressions.

root-value example

(root-value (parse/parse-string "#[1 2 3]"))
=> '(do 1 2 3)

16.2.4    from-value-string

^{:refer std.block.value/from-value-string :added "3.0"}

Reads a Clojure value from the block-value-string of a block. This is useful for blocks where the string representation for value generation differs from the raw string.

from-value-string example

(from-value-string (parse/parse-string "(+ 1 1)"))
=> '(+ 1 1)

16.2.5    list-value

^{:refer std.block.value/list-value :added "3.0"}

Returns the Clojure list value of an :list block.

list-value example

(list-value (parse/parse-string "(+ 1 1)"))
=> '(+ 1 1)

16.2.6    map-value

^{:refer std.block.value/map-value :added "3.0"}

Returns the Clojure map value of an :map block.

map-value example

(map-value (parse/parse-string "{1 2 3 4}"))
=> {1 2, 3 4}

(map-value (parse/parse-string "{1 2 3}"))
=> (throws)

16.2.7    set-value

^{:refer std.block.value/set-value :added "3.0"}

Returns the Clojure set value of an :set block.

set-value example

(set-value (parse/parse-string "#{1 2 3 4}"))
=> #{1 4 3 2}

16.2.8    vector-value

^{:refer std.block.value/vector-value :added "3.0"}

Returns the Clojure vector value of an :vector block.

vector-value example

(vector-value (parse/parse-string "[1 2 3 4]"))
=> [1 2 3 4]

16.2.9    deref-value

^{:refer std.block.value/deref-value :added "3.0"}

Returns the Clojure value of a :deref block (e.g., @atom).

deref-value example

(deref-value (parse/parse-string "@hello"))
=> '(deref hello)

16.2.10    meta-value

^{:refer std.block.value/meta-value :added "3.0"}

Returns the Clojure value of a :meta block (e.g., ^:dynamic x).

meta-value example

((juxt meta identity)
 (meta-value (parse/parse-string "^:dynamic {:a 1}")))
=> [{:dynamic true} {:a 1}]

((juxt meta identity)
 (meta-value (parse/parse-string "^String {:a 1}")))
=> [{:tag 'String} {:a 1}]

16.2.11    quote-value

^{:refer std.block.value/quote-value :added "3.0"}

Returns the Clojure value of a :quote block (e.g., 'symbol).

quote-value example

(quote-value (parse/parse-string "'hello"))
=> '(quote hello)

16.2.12    var-value

^{:refer std.block.value/var-value :added "3.0"}

Returns the Clojure value of a :var block (e.g., #'symbol).

var-value example

(var-value (parse/parse-string "#'hello"))
=> '(var hello)

16.2.13    hash-keyword-value

^{:refer std.block.value/hash-keyword-value :added "3.0"}

Returns the Clojure value of a :hash-keyword block (e.g., #:prefix{:key value}).

hash-keyword-value example

(hash-keyword-value (parse/parse-string "#:hello{:a 1 :b 2}"))
=> #:hello{:b 2, :a 1}

16.2.14    select-value

^{:refer std.block.value/select-value :added "3.0"}

Returns the Clojure value of a :select block (reader conditional, e.g., #?(:clj x)).

select-value example

(select-value (parse/parse-string "#?(:clj hello)"))
=> '(? {:clj hello})

16.2.15    select-splice-value

^{:refer std.block.value/select-splice-value :added "3.0"}

Returns the Clojure value of a :select-splice block (reader conditional splicing, e.g., #?@(:clj x)).

select-splice-value example

(select-splice-value (parse/parse-string "#?@(:clj hello)"))
=> '(?-splicing {:clj hello})

16.2.16    unquote-value

^{:refer std.block.value/unquote-value :added "3.0"}

Returns the Clojure value of an :unquote block (e.g., ~x).

unquote-value example

(unquote-value (parse/parse-string "~hello"))
=> '(unquote hello)

16.2.17    unquote-splice-value

^{:refer std.block.value/unquote-splice-value :added "3.0"}

Returns the Clojure value of an :unquote-splice block (e.g., ~@x).

unquote-splice-value example

(unquote-splice-value (parse/parse-string "~@hello"))
=> '(unquote-splicing hello)