-
Notifications
You must be signed in to change notification settings - Fork 60
WIP: dump asts #1041
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
WIP: dump asts #1041
Changes from all commits
88819e3
c88a9c9
4d596d8
312eee0
6f3e7fd
765a49f
46ab52f
b6ae20c
30f7454
d7af028
2c487c5
f6fd9a7
e1227bc
2c2e5b7
eff807f
3adb001
47c4f79
1e14581
3018283
900278e
abc13c9
a37f17f
09088d6
339eb3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
module DSL = struct | ||
type namedField = {name: string; value: oak} | ||
|
||
and oak = | ||
| Application of string * oak | ||
| Record of namedField list | ||
| Ident of string | ||
| Tuple of namedField list | ||
| List of oak list | ||
| String of string | ||
Comment on lines
+4
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to make sure I understand the justification of introducing another representation - the intention here is to have a generic pretty printer that we can use for debugging, correct? Where Provided the answer to the above is "yes", here's another important question:
Even if we don't use it right now, I'd like to see a PoC that it's doable before we commit to a specific pretty printing DSL. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I wanted to pattern match in I believe the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure! Have a look through this file: https://github.com/rescript-lang/rescript-vscode/blob/master/analysis/src/DumpAst.ml That prints (parts of) the parsetree, and marks structures with whether they hold the cursor or not (or if the loc is broken). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, in the tree traversal you would like to pass the cursor position and have that print something special if a node contains the cursor? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly, or if the loc is broken. Reason is that both of those are very important when working with things like hovers, autocomplete, etc. I don't need to see the actual locs I think, just if the cursor is in there and/or if the loc is broken. For actual locs, it's easy enough to print via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A couple of questions there: I'm not sure if I understand what a broken loc is. What does that mean?
I'm a little surprised by this. Can you elaborate on this? Or is it more a practical thing? A while ago you told me that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
end | ||
|
||
(** Transform the Oak types to string *) | ||
module CodePrinter = struct | ||
open DSL | ||
|
||
(** | ||
The idea is that we capture events in a context type. | ||
Doing this allows us to reason about the current state of the writer | ||
and whether the next expression fits on the current line or not. | ||
*) | ||
|
||
type writerEvents = | ||
| Write of string | ||
| WriteLine | ||
| IndentBy of int | ||
| UnindentBy of int | ||
|
||
type writerMode = Standard | TrySingleLine | ConfirmedMultiline | ||
|
||
(* Type representing the writer context during code printing | ||
|
||
- [indent_size] is the configured indentation size, typically 2 | ||
- [max_line_length] is the maximum line length before we break the line | ||
- [current_indent] is the current indentation size | ||
- [current_line_column] is the characters written on the current line | ||
- [line_count] is the number of lines written | ||
- [events] is the write events in reverse order, head event is last written | ||
- [mode] is the current writer mode (Standard or SingleLine) | ||
*) | ||
type context = { | ||
indent_size: int; | ||
max_line_length: int; | ||
current_indent: int; | ||
current_line_column: int; | ||
line_count: int; | ||
events: writerEvents list; | ||
mode: writerMode; | ||
} | ||
|
||
type appendEvents = context -> context | ||
|
||
let emptyContext = | ||
{ | ||
indent_size = 2; | ||
max_line_length = 120; | ||
current_indent = 0; | ||
current_line_column = 0; | ||
line_count = 0; | ||
events = []; | ||
mode = Standard; | ||
} | ||
|
||
(** Fold all the events in context into text *) | ||
let dump (ctx : context) = | ||
let buf = Buffer.create 1024 in | ||
let addSpaces n = Buffer.add_string buf (String.make n ' ') in | ||
|
||
List.fold_right | ||
(fun event current_indent -> | ||
match event with | ||
| Write str -> | ||
Buffer.add_string buf str; | ||
current_indent | ||
| WriteLine -> | ||
Buffer.add_char buf '\n'; | ||
addSpaces current_indent; | ||
current_indent | ||
| IndentBy n -> current_indent + n | ||
| UnindentBy n -> current_indent - n) | ||
ctx.events ctx.current_indent | ||
|> ignore; | ||
Buffer.contents buf | ||
|
||
let debug_context (ctx : context) = | ||
let mode = | ||
match ctx.mode with | ||
| Standard -> "Standard" | ||
| TrySingleLine -> "TrySingleLine" | ||
| ConfirmedMultiline -> "ConfirmedMultiline" | ||
in | ||
Format.printf | ||
"Current indent: %d, Current column: %d, # Lines: %d Events: %d, Mode: %s\n" | ||
ctx.current_indent ctx.current_line_column ctx.line_count | ||
(List.length ctx.events) mode; | ||
ctx | ||
|
||
let updateMode (newlineWasAdded : bool) (ctx : context) = | ||
match ctx.mode with | ||
| Standard -> ctx | ||
| ConfirmedMultiline -> ctx | ||
| TrySingleLine -> | ||
{ | ||
ctx with | ||
mode = | ||
(if newlineWasAdded || ctx.current_line_column > ctx.max_line_length | ||
then ConfirmedMultiline | ||
else TrySingleLine); | ||
} | ||
|
||
let id x = x | ||
|
||
(** add a write event to the context *) | ||
let write str ctx = | ||
{ | ||
ctx with | ||
events = Write str :: ctx.events; | ||
current_line_column = ctx.current_line_column + String.length str; | ||
} | ||
|> updateMode false | ||
|
||
(** compose two context transforming functions *) | ||
let compose_aux f g ctx = | ||
let fCtx = f ctx in | ||
match fCtx.mode with | ||
| ConfirmedMultiline -> fCtx | ||
| _ -> g fCtx | ||
|
||
let compose (fs : appendEvents list) ctx = | ||
let rec visit fs = | ||
match fs with | ||
| [] -> id | ||
| [f] -> f | ||
| f :: g :: rest -> visit (compose_aux f g :: rest) | ||
in | ||
visit fs ctx | ||
|
||
let sepNln ctx = | ||
{ | ||
ctx with | ||
events = WriteLine :: ctx.events; | ||
current_line_column = ctx.current_indent; | ||
line_count = ctx.line_count + 1; | ||
} | ||
|> updateMode true | ||
|
||
let sepSpace ctx = write " " ctx | ||
let sepComma ctx = write ", " ctx | ||
let sepSemi ctx = write "; " ctx | ||
let sepOpenT ctx = write "(" ctx | ||
let sepCloseT ctx = write ")" ctx | ||
let sepOpenR ctx = write "{" ctx | ||
let sepCloseR ctx = write "}" ctx | ||
let sepOpenL ctx = write "[" ctx | ||
let sepCloseL ctx = write "]" ctx | ||
let sepEq ctx = write " = " ctx | ||
let wrapInParentheses f = compose [sepOpenT; f; sepCloseT] | ||
let indent ctx = | ||
let nextIdent = ctx.current_indent + ctx.indent_size in | ||
{ | ||
ctx with | ||
current_indent = nextIdent; | ||
current_line_column = nextIdent; | ||
events = IndentBy ctx.indent_size :: ctx.events; | ||
} | ||
let unindent ctx = | ||
let nextIdent = ctx.current_indent - ctx.indent_size in | ||
{ | ||
ctx with | ||
current_indent = nextIdent; | ||
current_line_column = nextIdent; | ||
events = UnindentBy ctx.indent_size :: ctx.events; | ||
} | ||
|
||
let indentAndNln f = compose [indent; sepNln; f; unindent] | ||
|
||
let col (f : 't -> appendEvents) (intertwine : appendEvents) items ctx = | ||
let rec visit items ctx = | ||
match items with | ||
| [] -> ctx | ||
| [item] -> f item ctx | ||
| item :: rest -> | ||
let ctx' = compose [f item; intertwine] ctx in | ||
visit rest ctx' | ||
in | ||
visit items ctx | ||
|
||
let expressionFitsOnRestOfLine (f : appendEvents) (fallback : appendEvents) | ||
(ctx : context) = | ||
match ctx.mode with | ||
| ConfirmedMultiline -> ctx | ||
| _ -> ( | ||
let shortCtx = | ||
match ctx.mode with | ||
| Standard -> {ctx with mode = TrySingleLine} | ||
| _ -> ctx | ||
in | ||
let resultCtx = f shortCtx in | ||
match resultCtx.mode with | ||
| ConfirmedMultiline -> fallback ctx | ||
| TrySingleLine -> {resultCtx with mode = ctx.mode} | ||
| Standard -> | ||
failwith "Unexpected Standard mode after trying SingleLine mode") | ||
|
||
let rec genOak (oak : oak) : appendEvents = | ||
match oak with | ||
| Application (name, argument) -> genApplication name argument | ||
| Record record -> genRecord record | ||
| Ident ident -> genIdent ident | ||
| String str -> write (Format.sprintf "\"%s\"" str) | ||
| Tuple ts -> genTuple ts | ||
| List xs -> genList xs | ||
|
||
and genApplication (name : string) (argument : oak) : appendEvents = | ||
let short = compose [write name; sepOpenT; genOak argument; sepCloseT] in | ||
let long = | ||
compose | ||
[ | ||
write name; | ||
sepOpenT; | ||
(match argument with | ||
| List _ | Record _ -> genOak argument | ||
| _ -> compose [indentAndNln (genOak argument); sepNln]); | ||
sepCloseT; | ||
] | ||
in | ||
expressionFitsOnRestOfLine short long | ||
|
||
and genRecord (recordFields : namedField list) : appendEvents = | ||
let short = | ||
match recordFields with | ||
| [] -> compose [sepOpenR; sepCloseR] | ||
| fields -> | ||
compose | ||
[ | ||
sepOpenR; | ||
sepSpace; | ||
col genNamedField sepSemi fields; | ||
sepSpace; | ||
sepCloseR; | ||
] | ||
in | ||
let long = | ||
compose | ||
[ | ||
sepOpenR; | ||
indentAndNln (col genNamedField sepNln recordFields); | ||
sepNln; | ||
sepCloseR; | ||
] | ||
in | ||
expressionFitsOnRestOfLine short long | ||
|
||
and genTuple (oaks : namedField list) : appendEvents = | ||
let short = col genNamedField sepComma oaks in | ||
let long = col genNamedField sepNln oaks in | ||
expressionFitsOnRestOfLine short long | ||
|
||
and genIdent (ident : string) : appendEvents = write ident | ||
|
||
and genNamedField (field : namedField) : appendEvents = | ||
let genValue = | ||
match field.value with | ||
| Tuple _ -> compose [sepOpenT; genOak field.value; sepCloseT] | ||
| _ -> genOak field.value | ||
in | ||
let short = compose [write field.name; sepEq; genValue] in | ||
let long = | ||
compose | ||
[ | ||
write field.name; | ||
sepEq; | ||
(match field.value with | ||
| List _ | Record _ -> genOak field.value | ||
| _ -> indentAndNln genValue); | ||
] | ||
in | ||
expressionFitsOnRestOfLine short long | ||
|
||
and genList (items : oak list) : appendEvents = | ||
let genItem = function | ||
| Tuple _ as item -> wrapInParentheses (genOak item) | ||
| item -> genOak item | ||
in | ||
let short = | ||
match items with | ||
| [] -> compose [sepOpenL; sepCloseL] | ||
| _ -> | ||
compose | ||
[sepOpenL; sepSpace; col genItem sepSemi items; sepSpace; sepCloseL] | ||
in | ||
let long = | ||
compose | ||
[sepOpenL; indentAndNln (col genItem sepNln items); sepNln; sepCloseL] | ||
in | ||
expressionFitsOnRestOfLine short long | ||
end |
Uh oh!
There was an error while loading. Please reload this page.