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
- block?
- children
- code?
- comment
- comment?
- container
- container?
- contents
- cursor
- eof?
- expression?
- heal
- height
- info
- layout
- length
- linebreak?
- linespace?
- max-width
- modifier?
- newline
- newlines
- parse-first
- parse-root
- parse-string
- prefixed
- root
- space
- space?
- spaces
- string
- suffixed
- tab
- tabs
- tag
- token?
- type
- uneval
- value
- value-string
- verify
- void
- void?
- width
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]"
v 3.0
(defn block?
([obj]
(instance? IBlock obj)))
link
(block? (construct/void nil)) => true (block? (construct/token "hello")) => true
v 3.0
(defn block-children
([^IBlockContainer block]
(._children block)))
link
(->> (block-children (parse/parse-string "[1 2]")) (apply str)) => "1␣␣␣2"
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
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"
v 3.0
(defn comment-block?
([block]
(instance? CommentBlock block)))
link
(comment-block? (construct/comment ";;hello")) => true
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)])) => "( )"
v 3.0
(defn container-block?
([block]
(instance? ContainerBlock block)))
link
(container-block? (construct/block [])) => true
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]
v 3.0
(defn eof-block?
([block]
(and (void-block? block)
(= :eof (base/block-tag block)))))
link
(eof-block? (construct/void nil)) => true
v 3.0
(defn expression?
([^IBlock block]
(instance? IBlockExpression block)))
link
(expression? (construct/token 1.2)) => true
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?
v 3.0
(defn ^Number block-height
([^IBlock block]
(._height block)))
link
(block-height (construct/block ^:list [(construct/newline) (construct/newline)])) => 2
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}
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))))))"]
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
v 3.0
(defn linebreak-block?
([block]
(and (void-block? block)
(= :linebreak (base/block-tag block)))))
link
(linebreak-block? (construct/newline)) => true
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]
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
v 3.0
(defn modifier-block?
([block]
(instance? ModifierBlock block)))
link
(modifier-block? (construct/uneval)) => true
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"
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"
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}))
v 3.0
(defn ^Number block-prefixed
([^IBlock block]
(._prefixed block)))
link
(block-prefixed (construct/block #{})) => 2
v 3.0
(defn root
([children]
(block (container :root (construct-children children)))))
link
(str (root '[a b])) => "a b"
v 3.0
(defn space-block?
([block]
(and (void-block? block)
(= (.-character ^VoidBlock block) space))))
link
(space-block? (construct/space)) => true
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)) => ""
v 3.0
(defn ^Number block-suffixed
([^IBlock block]
(._suffixed block)))
link
(block-suffixed (construct/block #{})) => 1
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
v 3.0
(defn token-block?
([block]
(instance? TokenBlock block)))
link
(token-block? (construct/token "hello")) => true
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
v 3.0
(defn uneval
([]
(type/modifier-block :hash-uneval "#_" (fn [accumulator input] accumulator))))
link
(str (uneval)) => "#_"
v 3.0
(defn block-value
([^IBlockExpression block]
(._value block)))
link
(block-value (construct/token 1.2)) => 1.2
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"))"
v 3.0
(defn ^Boolean block-verify
([^IBlock block]
(._verify block)))
link
(block-verify (construct/token "hello")) => true
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.2 Base predicates and accessors
std.block.base defines the block taxonomy and the generic accessors used by every other namespace.
- block-children
- block-height
- block-info
- block-length
- block-prefixed
- block-string
- block-suffixed
- block-tag
- block-type
- block-value
- block-value-string
- block-verify
- block-width
- block?
- container?
- expression?
- modifier?
- replace-children
v 3.0
(defn block-children
([^IBlockContainer block]
(._children block)))
link
(->> (block-children (parse/parse-string "[1 2]")) (apply str)) => "1␣␣␣2"
v 3.0
(defn ^Number block-height
([^IBlock block]
(._height block)))
link
(block-height (construct/block ^:list [(construct/newline) (construct/newline)])) => 2
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}
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
v 3.0
(defn ^Number block-prefixed
([^IBlock block]
(._prefixed block)))
link
(block-prefixed (construct/block #{})) => 2
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)) => ""
v 3.0
(defn ^Number block-suffixed
([^IBlock block]
(._suffixed block)))
link
(block-suffixed (construct/block #{})) => 1
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
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
v 3.0
(defn block-value
([^IBlockExpression block]
(._value block)))
link
(block-value (construct/token 1.2)) => 1.2
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"))"
v 3.0
(defn ^Boolean block-verify
([^IBlock block]
(._verify block)))
link
(block-verify (construct/token "hello")) => true
v 3.0
(defn ^Number block-width
([^IBlock block]
(._width block)))
link
(block-width (construct/token 'hello)) => 5
v 3.0
(defn block?
([obj]
(instance? IBlock obj)))
link
(block? (construct/void nil)) => true (block? (construct/token "hello")) => true
v 3.0
(defn container?
([^IBlock block]
(instance? IBlockContainer block)))
link
(container? (parse/parse-string "[1 2 3]")) => true (container? (parse/parse-string " ")) => false
v 3.0
(defn expression?
([^IBlock block]
(instance? IBlockExpression block)))
link
(expression? (construct/token 1.2)) => true
v 3.0
(defn modifier?
([^IBlock block]
(instance? IBlockModifier block)))
link
(modifier? (construct/uneval)) => true
6.3 Construction
std.block.construct builds blocks from Clojure data and from raw components.
- add-child
- block
- comment
- container
- contents
- cursor
- indent-body
- line-width
- max-width
- newline
- newlines
- root
- space
- spaces
- string-token
- tab
- tabs
- token
- token-from-string
- uneval
- void
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]"
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]"
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"
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)])) => "( )"
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]
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]"
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]
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
v 3.0
(defn root
([children]
(block (container :root (construct-children children)))))
link
(str (root '[a b])) => "a b"
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\" ""
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]
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"
v 3.0
(defn uneval
([]
(type/modifier-block :hash-uneval "#_" (fn [accumulator input] accumulator))))
link
(str (uneval)) => "#_"
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
- delimiter-block?
- eof-block?
- parse-collection
- parse-comment
- parse-cons
- parse-first
- parse-hash
- parse-keyword
- parse-root
- parse-string
- parse-token
- read-dispatch
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}
v 3.0
(defn delimiter-block?
([block]
(= :delimiter (base/block-tag block))))
link
(delimiter-block? (binding [*end-delimiter* (first "}")] (-parse (reader/create "}")))) => true
v 3.0
(defn eof-block?
([block]
(= :eof (base/block-tag block))))
link
(eof-block? (-parse (reader/create ""))) => true
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}
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}
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)
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"
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"
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")
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"
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}))
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
6.5 Navigation
std.block.navigate provides the zipper API for editing block trees.
- backspace
- delete
- delete-left
- delete-right
- down
- find-next
- find-prev
- insert-all
- insert-newline
- insert-space
- insert-token
- insert-token-to-left
- insert-token-to-right
- left
- left*
- left-most
- left-most-token
- left-token
- line-info
- navigator
- navigator?
- next
- next-token
- parse-first
- parse-root
- parse-string
- prev
- prev-token
- replace
- replace-splice
- right
- right*
- right-most
- right-most-token
- right-token
- root-string
- swap
- tighten
- tighten-left
- tighten-right
- up
- update-children
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)"
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> (|)"
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> (|)"
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> (|)"
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]]"
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] )"
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]] )"
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"
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)"
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)"
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)"
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 )"
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 )"
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] )"
v 3.0
(defn left*
([nav]
(zip/step-left nav)))
link
(str (left* (from-status [1 (construct/cursor) 2]))) => "<0,2> [1| 2]"
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 )"
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)"
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)"
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}
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]"
v 3.0
(defn navigator?
([obj]
(and (instance? std.lib.zip.Zipper obj)
(= (:tag obj) :navigator))))
link
(navigator? (navigator [1 2 3 4])) => true
v 3.0
(defn next
([nav]
(zip/find-next nav base/expression?)))
link
(-> (parse-string "(#| [[3]] )") (next) (next) (next) str) => "<0,5> ( [[|3]] )"
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]])"
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)"
v 3.0
(defn parse-root
([string]
(navigator (parse/parse-root string))))
link
(str (parse-root "a b c")) => "<0,0> |a b c"
v 3.0
(defn parse-string
([string]
(from-status (parse/parse-string string))))
link
(str (parse-string "(2 #| 3 )")) => "<0,5> (2 | 3 )"
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]] )"
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]))"
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)"
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)"
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 )"
v 3.0
(defn right*
([nav]
(zip/step-right nav)))
link
(str (right* (from-status [(construct/cursor) 1 2]))) => "<0,2> [1| 2]"
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 )"
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])"
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)"
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]
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)"
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)"
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> (|)"
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> (|)"
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]]"
6.6 Layout and healing
std.block.layout formats forms; std.block.heal repairs delimiters.
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])"
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]
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
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))))))"]
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}
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?
6.7 Templates
std.block.template generates code from snippets with unquote holes.
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))"]
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?
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
IBlockProtocol: The foundational protocol that allstd.blocknodes implement. It defines the basic interface for querying block properties.nIBlockExpressionProtocol: ExtendsIBlockfor blocks that have an associated Clojure value.nIBlockModifierProtocol: ExtendsIBlockfor blocks that modify an accumulator (e.g., unevaluated forms).nIBlockContainerProtocol: ExtendsIBlockfor 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.,:collectionto#{: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\space,\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 (\newline,\return,\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 \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-definedstd.blockinstances for common void characters, optimizing their creation.nvoid-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: Thegridfunction takes a map of rules that define how different forms (e.g.,if-let,do,let) should be indented. These rules can specifyindent,bind, andscope.nindent: The base indentation level.nbind: 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-parsemultimethod: 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., \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 Fromstd.block.construct:block,void,space,spaces,newline,newlines,tab,tabs,comment,uneval,cursor,contents,container,root.n Fromstd.block.parse:parse-string,parse-root.n* Fromstd.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:
IBlockProtocol: The base protocol for all code blocks, defining methods like_type,_tag,_string,_length,_width,_height,_prefixed,_suffixed,_verify.nIBlockExpressionProtocol: For blocks that have an associated Clojure value, defining_valueand_value_string.nIBlockModifierProtocol: For blocks that modify an accumulator (e.g.,#_for unevaluated forms), defining_modify.nIBlockContainerProtocol: For blocks that contain other blocks (e.g., lists, vectors), defining_childrenand_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 anIBlock.nblock-*functions: Accessors for block properties (block-type,block-tag,block-string,block-length,block-width,block-height,block-prefixed,block-suffixed,block-verify).nexpression?: Checks if a block has a value.nblock-value: Returns the Clojure value of an expression block.nblock-value-string: Returns thepr-strrepresentation of the block's value.nmodifier?: Checks if a block is a modifier.nblock-modify: Applies a modifier's logic.ncontainer?: Checks if a block is a container.nblock-children: Returns the child blocks of a container.nreplace-children: Replaces the children of a container.nblock-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.ntag: Generic function to find a tag based on a set of checks.nvoid-tag,void?: Classifies and checks for void forms (whitespace, newlines).ntoken-tag,token?: Classifies and checks for token forms (numbers, symbols, keywords, strings).ncollection-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, andContainerBlock.n* Special Blocks:uneval(for#_) andcursor(for|) as modifier blocks.
Key Functions:
void,space,spaces,tab,tabs,newline,newlines: Create void blocks (whitespace, newlines, tabs).ncomment: Creates a comment block.nstring-token,token,token-from-string: Create token blocks for various literal types.ncontainer: Creates a container block (list, vector, map, set, root), handling delimiters and children.nuneval: Creates a modifier block for#_(unevaluated forms).ncursor: Creates a modifier block for|(used as a cursor in editing contexts).nconstruct-collection(multimethod): A multimethod for constructing blocks from Clojure collections (lists, vectors, maps, sets).nconstruct-children: Helper to convert a sequence of Clojure forms into a sequence of blocks, inserting correct spacing.nblock: A generic function to construct a block from a Clojure form (token or collection).nadd-child: Adds a child block to a container block.nempty: Creates an empty list container block.nroot: Creates a special container block representing the root of a code structure.ncontents: 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-rulesdefines 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 likeletorif.
Key Functions:
trim-left,trim-right: Remove void blocks from the ends of sequences.nsplit-lines: Splits a sequence of blocks into a sequence of lines based on linebreak blocks.nremove-starting-spaces: Removes leading space blocks from lines.nadjust-comments: Ensures comments are followed by newlines.nremove-extra-linebreaks: Collapses multiple consecutive linebreak blocks into a single one.ngrid-scope: Calculates indentation scope for child nodes.ngrid-rules: Determines indentation and binding rules for a given block type and symbol.nindent-bind: Calculates the number of lines to bind for indentation.nindent-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.readerIntegration: Usesclojure.tools.reader.reader-types/IndexingPushbackReaderfor character-by-character input processing, allowing for peeking, reading, and unreading characters.n Dispatch Mechanism: Usesread-dispatchand a multimethod-parseto 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.nparse-void: Parses whitespace, newlines, commas.nparse-comment: Parses a comment line.nparse-token: Parses numbers, symbols, booleans, ratios.nparse-keyword: Parses keywords (:key,::key).nparse-reader: Parses reader forms (e.g.,\c).nread-string-data: Parses string literals, handling newlines and escaped characters.nparse-collection: Parses collections ((),[],{},#{}).nparse-cons: Parses forms with prefixes (e.g.,',~,@,#).nparse-unquote,parse-select,parse-hash-uneval,parse-hash-cursor,parse-hash: Specific parsers for reader macros and prefixed forms.nparse-string: Parses an entire string into a single block.nparse-root: Parses a string into a root block, representing the entire input structure.neof-block?,delimiter-block?: Check for EOF or delimiter blocks.nread-whitespace: Reads a sequence of whitespace blocks.nparse-non-expressions: Parses blocks up to the next expression.nread-start: Verifies starting delimiters for collections/forms.nread-collection: Reads all child blocks within a collection, respecting delimiters.nread-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: Leveragesclojure.tools.reader.reader-types/IndexingPushbackReaderfor 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 anIndexingPushbackReaderfrom a string.nreader-position: Returns the current line and column of the reader.nthrow-reader: Throws an exception with detailed position information.nstep-char: Reads a character and advances the reader.nread-char: Reads the next character.nignore-char: Skips the next character.nunread-char: Pushes a character back onto the reader.npeek-char: Returns the next character without advancing the reader.nread-while: Reads characters as long as a predicate is true.nread-until: Reads characters until a predicate is true.nread-times: Reads characters a specified number of times.nread-repeatedly: Reads characters repeatedly until a stop condition.nread-include: Reads characters while accumulating non-expression blocks.nslurp: Reads the rest of the reader's input as a string.nread-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.nCommentBlock: Represents comments.nTokenBlock: Represents atomic values like numbers, symbols, keywords, strings.nContainerBlock: Represents collections (lists, vectors, maps, sets, root) and forms with prefixes (e.g.,'x).nModifierBlock: Represents reader macro modifiers (e.g.,#_,|).n* Dimensions: Trackswidthandheightof blocks for formatting.
Key Functions:
block-compare: A utility for comparing two blocks.nvoid-block?,void-block: Checks for and constructsVoidBlock.nspace-block?,linebreak-block?,linespace-block?,eof-block?,nil-void?: Specific predicates for types of void blocks.ncomment-block?,comment-block: Checks for and constructsCommentBlock.ntoken-block?,token-block: Checks for and constructsTokenBlock.ncontainer-width,container-height: Calculates dimensions forContainerBlock.ncontainer-string(multimethod): Generates the string representation for different container types.ncontainer-value-string: Generates a string representation of the value of a container block (used forblock-value-string).ncontainer-block?,container-block: Checks for and constructsContainerBlock.n*modifier-block?,modifier-block: Checks for and constructsModifierBlock.
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: AppliesIBlockModifierlogic to a sequence of blocks.nchild-values: Extracts the Clojure values of child blocks within a container, applying modifiers.nroot-value,list-value,map-value,set-value,vector-value: Extract values from container blocks.nderef-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.nfrom-value-string: Usesread-stringon theblock-value-stringto 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.nVoidBlock: Represents non-code elements like spaces, newlines, tabs, commas, or EOF signals.nCommentBlock: Represents single-line comments starting with;.nTokenBlock: Represents literal values, symbols, keywords, numbers, strings, etc.nContainerBlock: 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., \space, \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)