Skip to content

Commit b6ae20c

Browse files
committed
Extract printer from tast transformer
1 parent 46ab52f commit b6ae20c

File tree

3 files changed

+288
-248
lines changed

3 files changed

+288
-248
lines changed

tools/src/prettier_printer.ml

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
(* open Analysis *)
2+
3+
module DSL = struct
4+
type application = {name: string; argument: oak}
5+
6+
and namedField = {name: string; value: oak}
7+
8+
and oak =
9+
| Application of application
10+
| Record of namedField list
11+
| Ident of string
12+
| Tuple of namedField list
13+
| List of oak list
14+
| String of string
15+
end
16+
17+
(** Transform the Oak types to string *)
18+
module CodePrinter = struct
19+
open DSL
20+
21+
(**
22+
The idea is that we capture events in a context type.
23+
Doing this allows us to reason about the current state of the writer
24+
and whether the next expression fits on the current line or not.
25+
26+
*)
27+
28+
type writerEvents =
29+
| Write of string
30+
| WriteLine
31+
| IndentBy of int
32+
| UnindentBy of int
33+
34+
type context = {
35+
indent_size: int;
36+
max_line_length: int;
37+
current_indent: int;
38+
current_line_column: int;
39+
events: writerEvents list;
40+
line_count: int;
41+
nesting_level: int;
42+
}
43+
44+
type appendEvents = context -> context
45+
46+
let emptyContext =
47+
{
48+
indent_size = 2;
49+
max_line_length = 80;
50+
current_indent = 0;
51+
current_line_column = 0;
52+
events = [];
53+
line_count = 0;
54+
nesting_level = 0;
55+
}
56+
57+
(** Fold all the events in context into text *)
58+
let dump (ctx : context) =
59+
let buf = Buffer.create 1024 in
60+
let addSpaces n = Buffer.add_string buf (String.make n ' ') in
61+
62+
List.fold_right
63+
(fun event current_indent ->
64+
match event with
65+
| Write str ->
66+
Buffer.add_string buf str;
67+
current_indent
68+
| WriteLine ->
69+
Buffer.add_char buf '\n';
70+
addSpaces current_indent;
71+
current_indent
72+
| IndentBy n -> current_indent + n
73+
| UnindentBy n -> current_indent - n)
74+
ctx.events ctx.current_indent
75+
|> ignore;
76+
Buffer.contents buf
77+
78+
let debug_context (ctx : context) =
79+
Format.printf "Current indent: %d, Current line: %d, Events: %d\n"
80+
ctx.current_indent ctx.line_count (List.length ctx.events);
81+
ctx
82+
83+
let increase_nesting ctx = {ctx with nesting_level = ctx.nesting_level + 1}
84+
85+
let decrease_nesting ctx =
86+
{ctx with nesting_level = max 0 (ctx.nesting_level - 1)}
87+
88+
(* Type representing the writer context during code printing
89+
90+
- [indent_size] is the configured indentation size, typically 2
91+
- [current_indent] is the current indentation size
92+
- [current_line_column] is the characters written on the current line
93+
- [events] is the write events in reverse order, head event is last written
94+
*)
95+
96+
let id x = x
97+
98+
(** add a write event to the context *)
99+
let ( !- ) str ctx =
100+
{
101+
ctx with
102+
events = Write str :: ctx.events;
103+
current_line_column = ctx.current_line_column + String.length str;
104+
}
105+
106+
(** compose two context transforming functions *)
107+
let ( +> ) f g ctx = g (f ctx)
108+
109+
let sepNln ctx =
110+
{
111+
ctx with
112+
events = WriteLine :: ctx.events;
113+
current_line_column = ctx.current_indent;
114+
line_count = ctx.line_count + 1;
115+
}
116+
let sepSpace ctx = !-" " ctx
117+
let sepComma ctx = !-", " ctx
118+
let sepSemi ctx = !-"; " ctx
119+
let sepOpenT ctx = !-"(" ctx
120+
let sepCloseT ctx = !-")" ctx
121+
let sepOpenR ctx = !-"{" ctx
122+
let sepCloseR ctx = !-"}" ctx
123+
let sepOpenL ctx = !-"[" ctx
124+
let sepCloseL ctx = !-"]" ctx
125+
let sepEq ctx = !-" = " ctx
126+
let wrapInParentheses f = sepOpenT +> f +> sepCloseT
127+
let indent ctx =
128+
let nextIdent = ctx.current_indent + ctx.indent_size in
129+
{
130+
ctx with
131+
current_indent = nextIdent;
132+
current_line_column = nextIdent;
133+
events = IndentBy ctx.indent_size :: ctx.events;
134+
}
135+
let unindent ctx =
136+
let nextIdent = ctx.current_indent - ctx.indent_size in
137+
{
138+
ctx with
139+
current_indent = nextIdent;
140+
current_line_column = nextIdent;
141+
events = UnindentBy ctx.indent_size :: ctx.events;
142+
}
143+
144+
let indentAndNln f = indent +> sepNln +> f +> unindent
145+
146+
let col (f : 't -> appendEvents) (intertwine : appendEvents) items ctx =
147+
let rec visit items ctx =
148+
match items with
149+
| [] -> ctx
150+
| [item] -> f item ctx
151+
| item :: rest ->
152+
let ctx' = (f item +> intertwine) ctx in
153+
visit rest ctx'
154+
in
155+
visit items ctx
156+
157+
let expressionFitsOnRestOfLine (f : appendEvents) (fallback : appendEvents)
158+
(ctx : context) =
159+
(* create a short context and check if the expression fits on the current line *)
160+
let shortCtx = f ctx in
161+
if
162+
ctx.line_count == shortCtx.line_count
163+
&& shortCtx.current_line_column <= ctx.max_line_length
164+
then shortCtx
165+
else fallback ctx
166+
167+
let rec genOak (oak : oak) : appendEvents =
168+
match oak with
169+
| Application application -> genApplication application
170+
| Record record -> genRecord record
171+
| Ident ident -> genIdent ident
172+
| String str -> !-(Format.sprintf "\"%s\"" str)
173+
| Tuple ts -> genTuple ts
174+
| List xs -> genList xs
175+
176+
and genApplication (application : application) : appendEvents =
177+
let short =
178+
!-(application.name) +> sepOpenT
179+
+> genOak application.argument
180+
+> sepCloseT
181+
in
182+
let long =
183+
!-(application.name) +> sepOpenT
184+
+> (match application.argument with
185+
| List _ | Record _ -> genOak application.argument
186+
| _ -> indentAndNln (genOak application.argument) +> sepNln)
187+
+> sepCloseT
188+
in
189+
expressionFitsOnRestOfLine short long
190+
191+
and genRecord (recordFields : namedField list) : appendEvents =
192+
let short =
193+
match recordFields with
194+
| [] -> sepOpenR +> sepCloseR
195+
| fields ->
196+
sepOpenR +> sepSpace
197+
+> col genNamedField sepSemi fields
198+
+> sepSpace +> sepCloseR
199+
in
200+
let long =
201+
sepOpenR
202+
+> indentAndNln (col genNamedField sepNln recordFields)
203+
+> sepNln +> sepCloseR
204+
in
205+
expressionFitsOnRestOfLine short long
206+
207+
and genTuple (oaks : namedField list) : appendEvents =
208+
let short = col genNamedField sepComma oaks in
209+
let long = col genNamedField sepNln oaks in
210+
expressionFitsOnRestOfLine short long
211+
212+
and genIdent (ident : string) : appendEvents = !-ident
213+
214+
and genNamedField (field : namedField) : appendEvents =
215+
let short = !-(field.name) +> sepEq +> genOak field.value in
216+
let long =
217+
!-(field.name) +> sepEq
218+
+>
219+
match field.value with
220+
| List _ | Record _ -> genOak field.value
221+
| _ -> indentAndNln (genOak field.value)
222+
in
223+
expressionFitsOnRestOfLine short long
224+
225+
and genList (items : oak list) : appendEvents =
226+
let genItem = function
227+
| Tuple _ as item -> wrapInParentheses (genOak item)
228+
| item -> genOak item
229+
in
230+
let short =
231+
match items with
232+
| [] -> sepOpenL +> sepCloseL
233+
| _ ->
234+
sepOpenL +> sepSpace +> col genItem sepSemi items +> sepSpace
235+
+> sepCloseL
236+
in
237+
let long =
238+
sepOpenL +> indentAndNln (col genItem sepNln items) +> sepNln +> sepCloseL
239+
in
240+
expressionFitsOnRestOfLine short long
241+
end
242+
243+
open DSL
244+
245+
let oak =
246+
DSL.Record
247+
[
248+
{
249+
name = "zig";
250+
value =
251+
DSL.Record
252+
[
253+
{name = "foo"; value = Ident "baaaaaaaaaaaaaaaaar"};
254+
{name = "member"; value = Ident "Zigbaaaaaaaaar"};
255+
];
256+
};
257+
{
258+
name = "roxas";
259+
value =
260+
List
261+
[
262+
Ident "jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj";
263+
Ident "meeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
264+
DSL.Record
265+
[
266+
{name = "foo"; value = Ident "baaaaaaaaaaaaaaaaar"};
267+
{name = "member"; value = Ident "Zigbaaaaaaaaar"};
268+
];
269+
];
270+
};
271+
{name = "foo"; value = Ident "baaaaaaaaaaaaaaaaar"};
272+
]
273+
274+
(* let _ =
275+
CodePrinter.genOak oak {CodePrinter.emptyContext with max_line_length = 20}
276+
|> CodePrinter.dump |> Format.printf "%s\n" *)
277+
278+
(*
279+
Interpret using ocaml /home/nojaf/projects/rescript-vscode/tools/src/prettier_printer.ml
280+
*)

0 commit comments

Comments
 (0)