Skip to content

Commit 15a99bc

Browse files
add debug function
1 parent 05a417d commit 15a99bc

File tree

5 files changed

+236
-0
lines changed

5 files changed

+236
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Remove the `doc.to_string_builder` method.
66
- The `doc` module gains the `zero_width_string` function
7+
- The `doc` module gains the `debug` function
78

89
## v1.3.0 - 2023-01-03
910

birdie_snapshots/debug_1.accepted

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
version: 1.0.4
3+
title: debug 1
4+
---
5+
[
6+
²⟨
7+
"{" . space . "title: \"The sundial\"," . space .
8+
"author: \"Shirley Jackson\"," . space . "publication_year: 1958.0," . space .
9+
"read: true," . space . "characters: " .
10+
[
11+
²⟨
12+
"[" . space . "\"Mrs. Halloran\"" . "," . space . "\"Essex\"" . "," .
13+
space . "\"Captain Scarabombardon\""
14+
⟩ . space . "]"
15+
] .
16+
"," . space . "average_rating: 5.0," . space . "ratings: " .
17+
[
18+
²⟨
19+
"[" . space .
20+
[
21+
²⟨
22+
"{" . space . "from: \"Ben\"," . space . "value: 5.0"
23+
⟩ . space . "}"
24+
] .
25+
"," . space .
26+
[
27+
²⟨
28+
"{" . space . "from: \"Giacomo\"," . space . "value: 5.0"
29+
⟩ . space . "}"
30+
]
31+
⟩ . space . "]"
32+
]
33+
⟩ . space . "}"
34+
]

birdie_snapshots/debug_2.accepted

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
version: 1.0.4
3+
title: debug 2
4+
---
5+
"[ E001 ]: Unused function" . lf . "1 | fn greet(message: String) -> Nil {" .
6+
⁷⟨
7+
lf . "┬────" . lf . "╰─ " .
8+
³⟨
9+
[
10+
[
11+
"This" . flex_space . "function" . flex_space . "is" . flex_space .
12+
"unused!"
13+
] .
14+
lf .
15+
[
16+
"You" . flex_space . "can" . flex_space . "safely" . flex_space .
17+
"remove" . flex_space . "it" . flex_space . "or" . flex_space . "make" .
18+
flex_space . "it" . flex_space . "public" . flex_space . "with" .
19+
flex_space . "the" . flex_space . "`pub`" . flex_space . "keyword."
20+
]
21+
]
22+
23+
⟩ . lf² . "[ E011 ]: Unknown variable" . lf .
24+
"2 | println(\"Hello\" <> message)" .
25+
⁶⟨
26+
lf . "┬──────" . lf . "╰─ " .
27+
³⟨
28+
[
29+
[
30+
"The" . flex_space . "name" . flex_space . "`println`" . flex_space .
31+
"is" . flex_space . "not" . flex_space . "in" . flex_space . "scope" .
32+
flex_space . "here."
33+
] .
34+
lf .
35+
[
36+
"Did" . flex_space . "you" . flex_space . "mean" . flex_space . "to" .
37+
flex_space . "use" . flex_space . "`io.println`?"
38+
]
39+
]
40+
41+

src/glam/doc.gleam

+140
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import gleam/int
12
import gleam/list
3+
import gleam/order.{Eq, Gt, Lt}
24
import gleam/string
35

46
/// A document that can be pretty printed with `to_string`.
@@ -621,6 +623,144 @@ fn do_to_string(
621623
}
622624
}
623625

626+
// --- DEBUGGING UTILITIES -----------------------------------------------------
627+
628+
const debug_nesting = 2
629+
630+
/// Returns a debug version of the given document that can be pretty printed
631+
/// to see the structure of a document.
632+
///
633+
/// This can help you see how your data structures get turned into documents
634+
/// and check if the document is what you'd expect.
635+
///
636+
/// - `group`s are surrounded by square brackets.
637+
/// - `nest`s are surrounded by angle brackets and have a smal superscript
638+
/// with the nesting.
639+
/// - `concat`enated documents are separated by dots.
640+
/// - `break`s are rendered surrounded by curly brackets and show both the
641+
/// broken and unbroken versions.
642+
/// - `line`s are rendered as the string `lf` followed by a superscript
643+
/// number of lines.
644+
///
645+
pub fn debug(document: Document) -> Document {
646+
case document {
647+
Text(text, _length) -> {
648+
let escaped = string.replace(each: "\"", with: "\\\"", in: text)
649+
from_string("\"" <> escaped <> "\"")
650+
}
651+
652+
ForceBreak(doc) -> parenthesise(debug(doc), "force(", ")")
653+
654+
Group(doc) ->
655+
parenthesise(debug(doc), "[", "]")
656+
|> force_break
657+
658+
Nest(doc, indentation) ->
659+
parenthesise(debug(doc), superscript_number(indentation) <> "⟨", "⟩")
660+
|> force_break
661+
662+
Break(" ", "") -> from_string("space")
663+
Break(unbroken, broken) ->
664+
from_string("{ \"" <> unbroken <> "\", \"" <> broken <> "\" }")
665+
666+
FlexBreak(" ", "") -> from_string("flex_space")
667+
FlexBreak(unbroken, broken) ->
668+
from_string("flex{ \"" <> unbroken <> "\", \"" <> broken <> "\" }")
669+
670+
Line(size) ->
671+
case int.compare(size, 1) {
672+
Lt | Eq -> from_string("lf")
673+
Gt -> from_string("lf" <> superscript_number(size))
674+
}
675+
676+
Concat(docs) ->
677+
split_groups(flatten(docs))
678+
|> list.map(fn(docs) {
679+
case docs {
680+
[] -> panic as "empty"
681+
_ -> Nil
682+
}
683+
list.map(docs, debug)
684+
|> join(with: flex_break(" . ", " ."))
685+
|> group
686+
})
687+
|> join(with: concat([flex_break(" . ", " ."), line]))
688+
}
689+
}
690+
691+
fn parenthesise(document: Document, open: String, close: String) -> Document {
692+
[
693+
from_string(open),
694+
nest(line, by: debug_nesting),
695+
nest(document, by: debug_nesting),
696+
line,
697+
from_string(close),
698+
]
699+
|> concat
700+
|> group
701+
}
702+
703+
fn flatten(docs: List(Document)) -> List(Document) {
704+
do_flatten(docs, [])
705+
}
706+
707+
fn do_flatten(docs: List(Document), acc: List(Document)) -> List(Document) {
708+
case docs {
709+
[] -> list.reverse(acc)
710+
[one] -> list.reverse([one, ..acc])
711+
[Concat(one), Concat(two), ..rest] ->
712+
do_flatten([Concat(list.append(one, two)), ..rest], acc)
713+
[Text(one, len_one), Text(two, len_two), ..rest] ->
714+
do_flatten([Text(one <> two, len_one + len_two), ..rest], acc)
715+
[one, two, ..rest] -> do_flatten([two, ..rest], [one, ..acc])
716+
}
717+
}
718+
719+
fn split_groups(docs: List(Document)) -> List(List(Document)) {
720+
do_split_groups(docs, [], [])
721+
}
722+
723+
fn do_split_groups(
724+
docs: List(Document),
725+
current_group: List(Document),
726+
acc: List(List(Document)),
727+
) -> List(List(Document)) {
728+
case docs {
729+
[] ->
730+
case current_group {
731+
[] -> list.reverse(acc)
732+
_ -> list.reverse([list.reverse(current_group), ..acc])
733+
}
734+
735+
[Group(_) as doc, ..rest] ->
736+
case current_group {
737+
[] -> do_split_groups(rest, [], [[doc], ..acc])
738+
_ ->
739+
do_split_groups(rest, [], [[doc], list.reverse(current_group), ..acc])
740+
}
741+
[doc, ..rest] -> do_split_groups(rest, [doc, ..current_group], acc)
742+
}
743+
}
744+
745+
fn superscript_number(number: Int) -> String {
746+
let assert Ok(digits) = int.digits(number, 10)
747+
use acc, digit <- list.fold(over: digits, from: "")
748+
let digit = case digit {
749+
0 -> "⁰"
750+
1 -> "¹"
751+
2 -> "²"
752+
3 -> "³"
753+
4 -> "⁴"
754+
5 -> "⁵"
755+
6 -> "⁶"
756+
7 -> "⁷"
757+
8 -> "⁸"
758+
9 -> "⁹"
759+
_ -> panic as "not a digit"
760+
}
761+
acc <> digit
762+
}
763+
624764
// --- UTILITY FUNCTIONS -------------------------------------------------------
625765

626766
fn indentation(size: Int) -> String {

test/glam/doc_test.gleam

+20
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import gleam/list
22
import gleam/string
33
import gleeunit/should
44
import glam/doc
5+
import examples/json
6+
import examples/error_messages
57
import birdie
68

79
pub fn append_test() {
@@ -228,3 +230,21 @@ pub fn zero_width_string_example_test() {
228230
|> doc.to_string(20)
229231
|> birdie.snap(title: "zero width string 2")
230232
}
233+
234+
pub fn debug_1_test() {
235+
json.example_json()
236+
|> json.json_to_doc
237+
|> doc.debug
238+
|> doc.to_string(80)
239+
|> birdie.snap(title: "debug 1")
240+
}
241+
242+
pub fn debug_2_test() {
243+
error_messages.errors_to_doc(
244+
error_messages.example_source_code,
245+
error_messages.example_errors(),
246+
)
247+
|> doc.debug
248+
|> doc.to_string(80)
249+
|> birdie.snap(title: "debug 2")
250+
}

0 commit comments

Comments
 (0)