1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
if polyglot#init#is_disabled(expand('<sfile>:p'), 'smt2', 'autoload/smt2/formatter.vim')
finish
endif
" Formatting requires a rather recent Vim version
if !((v:version > 802) || (v:version == 802 && has("patch2725")))
const s:errmsg_oldvim = "Vim >= 8.2.2725 required for auto-formatting"
"Dummies
function! smt2#formatter#FormatCurrentParagraph()
echoerr s:errmsg_oldvim
endfunction
function! smt2#formatter#FormatAllParagraphs()
echoerr s:errmsg_oldvim
endfunction
finish
endif
vim9script
# ------------------------------------------------------------------------------
# Config
# ------------------------------------------------------------------------------
# Length of "short" S-expressions
if !exists("g:smt2_formatter_short_length")
g:smt2_formatter_short_length = 80
endif
# String to use for indentation
if !exists("g:smt2_formatter_indent_str")
g:smt2_formatter_indent_str = ' '
endif
# ------------------------------------------------------------------------------
# Formatter
# ------------------------------------------------------------------------------
def FitsOneLine(ast: dict<any>): bool
# A paragraph with several entries should not be formatted in one line
if ast.kind ==# 'Paragraph' && len(ast.value) != 1
return false
endif
return ast.pos_to - ast.pos_from < g:smt2_formatter_short_length && !ast.contains_comment
enddef
def FormatOneLine(ast: dict<any>): string
if ast.kind ==# 'Atom'
return ast.value.lexeme
elseif ast.kind ==# 'SExpr'
var formatted = []
for expr in ast.value
call formatted->add(expr->FormatOneLine())
endfor
return '(' .. formatted->join(' ') .. ')'
elseif ast.kind ==# 'Paragraph'
return ast.value[0]->FormatOneLine()
endif
throw 'Cannot format AST node: ' .. string(ast)
return '' # Unreachable
enddef
def Format(ast: dict<any>, indent = 0): string
const indent_str = repeat(g:smt2_formatter_indent_str, indent)
if ast.kind ==# 'Atom'
return indent_str .. ast.value.lexeme
elseif ast.kind ==# 'SExpr'
# Short expression -- avoid line breaks
if ast->FitsOneLine()
return indent_str .. ast->FormatOneLine()
endif
# Long expression -- break lines and indent subexpressions.
# Don't break before first subexpression if it's an atom
# Note: ast.value->empty() == false; otherwise it would fit in one line
var formatted = []
if (ast.value[0].kind ==# 'Atom')
call formatted->add(ast.value[0]->Format(0))
else
call formatted->add("\n" .. ast.value[0]->Format(indent + 1))
endif
for child in ast.value[1 :]
call formatted->add(child->Format(indent + 1))
endfor
return indent_str .. "(" .. formatted->join("\n") .. ")"
elseif ast.kind ==# 'Paragraph'
var formatted = []
for child in ast.value
call formatted->add(child->Format())
endfor
return formatted->join("\n")
endif
throw 'Cannot format AST node: ' .. string(ast)
return '' # Unreachable
enddef
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
def smt2#formatter#FormatCurrentParagraph()
const cursor = getpos('.')
const ast = smt2#parser#ParseCurrentParagraph()
# Identify on which end of the buffer we are (to fix newlines later)
silent! normal! {
const is_first_paragraph = line('.') == 1
silent! normal! }
const is_last_paragraph = line('.') == line('$')
# Replace paragraph by formatted lines
const lines = split(Format(ast), '\n')
silent! normal! {d}
if is_last_paragraph && !is_first_paragraph
call append('.', [''] + lines)
else
call append('.', lines + [''])
endif
# Remove potentially introduced first empty line
if is_first_paragraph | silent! :1delete | endif
# Restore cursor position
call setpos('.', cursor)
enddef
def smt2#formatter#FormatAllParagraphs()
const cursor = getpos('.')
const asts = smt2#parser#ParseAllParagraphs()
# Clear buffer & insert formatted paragraphs
silent! :1,$delete
for ast in asts
const lines = split(Format(ast), '\n') + ['']
call append('$', lines)
endfor
# Remove first & trailing empty lines
silent! :1delete
silent! :$delete
# Restore cursor position
call setpos('.', cursor)
enddef
|