xref: /vim-8.2.3635/runtime/indent/cobol.vim (revision 6e649224)
1" Vim indent file
2" Language:	cobol
3" Maintainer: Ankit Jain <[email protected]>
4"     (formerly Tim Pope <[email protected]>)
5" $Id: cobol.vim,v 1.1 2007/05/05 18:08:19 vimboss Exp $
6" Last Update:	By Ankit Jain on 22.03.2019
7" Ankit Jain      22.03.2019     Changes & fixes:
8"                                Allow chars in 1st 6 columns
9"                                #C22032019
10" Ankit Jain      24.09.2021     add b:undo_indent (request by tpope)
11
12if exists("b:did_indent")
13    finish
14endif
15let b:did_indent = 1
16
17setlocal expandtab
18setlocal indentexpr=GetCobolIndent(v:lnum)
19setlocal indentkeys&
20setlocal indentkeys+=0<*>,0/,0$,0=01,=~division,=~section,0=~end,0=~then,0=~else,0=~when,*<Return>,.
21
22let b:undo_indent = "setlocal expandtab< indentexpr< indentkeys<"
23
24" Only define the function once.
25if exists("*GetCobolIndent")
26    finish
27endif
28
29let s:skip = 'getline(".") =~ "^.\\{6\\}[*/$-]\\|\"[^\"]*\""'
30
31function! s:prevgood(lnum)
32    " Find a non-blank line above the current line.
33    " Skip over comments.
34    let lnum = a:lnum
35    while lnum > 0
36        let lnum = prevnonblank(lnum - 1)
37        let line = getline(lnum)
38        if line !~? '^\s*[*/$-]' && line !~? '^.\{6\}[*/$CD-]'
39            break
40        endif
41    endwhile
42    return lnum
43endfunction
44
45function! s:stripped(lnum)
46    return substitute(strpart(getline(a:lnum),0,72),'^\s*','','')
47endfunction
48
49function! s:optionalblock(lnum,ind,blocks,clauses)
50    let ind = a:ind
51    let clauses = '\c\<\%(\<NOT\s\+\)\@<!\%(NOT\s\+\)\=\%('.a:clauses.'\)'
52    let begin = '\c-\@<!\<\%('.a:blocks.'\)\>'
53    let beginfull = begin.'\ze.*\%(\n\%(\s*\%([*/$-].*\)\=\n\)*\)\=\s*\%('.clauses.'\)'
54    let end   = '\c\<end-\%('.a:blocks.'\)\>\|\%(\.\%( \|$\)\)\@='
55    let cline = s:stripped(a:lnum)
56    let line  = s:stripped(s:prevgood(a:lnum))
57    if cline =~? clauses "&& line !~? '^search\>'
58        call cursor(a:lnum,1)
59        let lastclause = searchpair(beginfull,clauses,end,'bWr',s:skip)
60        if getline(lastclause) =~? clauses && s:stripped(lastclause) !~? '^'.begin
61            let ind = indent(lastclause)
62        elseif lastclause > 0
63            let ind = indent(lastclause) + shiftwidth()
64            "let ind = ind + shiftwidth()
65        endif
66    elseif line =~? clauses && cline !~? end
67        let ind = ind + shiftwidth()
68    endif
69    return ind
70endfunction
71
72function! GetCobolIndent(lnum) abort
73    let minshft = 6
74    let ashft = minshft + 1
75    let bshft = ashft + 4
76    " (Obsolete) numbered lines
77    " #C22032019: Columns 1-6 could have alphabets as well as numbers
78    "if getline(a:lnum) =~? '^\s*\d\{6\}\%($\|[ */$CD-]\)'
79    if getline(a:lnum) =~? '^\s*[a-zA-Z0-9]\{6\}\%($\|[ */$CD-]\)'
80        return 0
81    endif
82    let cline = s:stripped(a:lnum)
83    " Comments, etc. must start in the 7th column
84    if cline =~? '^[*/$-]'
85        return minshft
86    elseif cline =~# '^[CD]' && indent(a:lnum) == minshft
87        return minshft
88    endif
89    " Divisions, sections, and file descriptions start in area A
90    if cline =~? '\<\(DIVISION\|SECTION\)\%($\|\.\)' || cline =~? '^[FS]D\>'
91        return ashft
92    endif
93    " Fields
94    if cline =~? '^0*\(1\|77\)\>'
95        return ashft
96    endif
97    if cline =~? '^\d\+\>'
98        let cnum = matchstr(cline,'^\d\+\>')
99        let default = 0
100        let step = -1
101        while step < 2
102        let lnum = a:lnum
103        while lnum > 0 && lnum < line('$') && lnum > a:lnum - 500 && lnum < a:lnum + 500
104            let lnum = step > 0 ? nextnonblank(lnum + step) : prevnonblank(lnum + step)
105            let line = getline(lnum)
106            let lindent = indent(lnum)
107            if line =~? '^\s*\d\+\>'
108                let num = matchstr(line,'^\s*\zs\d\+\>')
109                if 0+cnum == num
110                    return lindent
111                elseif 0+cnum > num && default < lindent + shiftwidth()
112                    let default = lindent + shiftwidth()
113                endif
114            elseif lindent < bshft && lindent >= ashft
115                break
116            endif
117        endwhile
118        let step = step + 2
119        endwhile
120        return default ? default : bshft
121    endif
122    let lnum = s:prevgood(a:lnum)
123    " Hit the start of the file, use "zero" indent.
124    if lnum == 0
125        return ashft
126    endif
127    " Initial spaces are ignored
128    let line = s:stripped(lnum)
129    let ind = indent(lnum)
130    " Paragraphs.  There may be some false positives.
131    if cline =~? '^\(\a[A-Z0-9-]*[A-Z0-9]\|\d[A-Z0-9-]*\a\)\.' "\s*$'
132        if cline !~? '^EXIT\s*\.' && line =~? '\.\s*$'
133            return ashft
134        endif
135    endif
136    " Paragraphs in the identification division.
137    "if cline =~? '^\(PROGRAM-ID\|AUTHOR\|INSTALLATION\|' .
138                "\ 'DATE-WRITTEN\|DATE-COMPILED\|SECURITY\)\>'
139        "return ashft
140    "endif
141    if line =~? '\.$'
142        " XXX
143        return bshft
144    endif
145    if line =~? '^PERFORM\>'
146        let perfline = substitute(line, '\c^PERFORM\s*', "", "")
147        if perfline =~? '^\%(\k\+\s\+TIMES\)\=\s*$'
148            let ind = ind + shiftwidth()
149        elseif perfline =~? '^\%(WITH\s\+TEST\|VARYING\|UNTIL\)\>.*[^.]$'
150            let ind = ind + shiftwidth()
151        endif
152    endif
153    if line =~? '^\%(IF\|THEN\|ELSE\|READ\|EVALUATE\|SEARCH\|SELECT\)\>'
154        let ind = ind + shiftwidth()
155    endif
156    let ind = s:optionalblock(a:lnum,ind,'ADD\|COMPUTE\|DIVIDE\|MULTIPLY\|SUBTRACT','ON\s\+SIZE\s\+ERROR')
157    let ind = s:optionalblock(a:lnum,ind,'STRING\|UNSTRING\|ACCEPT\|DISPLAY\|CALL','ON\s\+OVERFLOW\|ON\s\+EXCEPTION')
158    if cline !~? '^AT\s\+END\>' || line !~? '^SEARCH\>'
159        let ind = s:optionalblock(a:lnum,ind,'DELETE\|REWRITE\|START\|WRITE\|READ','INVALID\s\+KEY\|AT\s\+END\|NO\s\+DATA\|AT\s\+END-OF-PAGE')
160    endif
161    if cline =~? '^WHEN\>'
162        call cursor(a:lnum,1)
163        " We also search for READ so that contained AT ENDs are skipped
164        let lastclause = searchpair('\c-\@<!\<\%(SEARCH\|EVALUATE\|READ\)\>','\c\<\%(WHEN\|AT\s\+END\)\>','\c\<END-\%(SEARCH\|EVALUATE\|READ\)\>','bW',s:skip)
165        let g:foo = s:stripped(lastclause)
166        if s:stripped(lastclause) =~? '\c\<\%(WHEN\|AT\s\+END\)\>'
167            "&& s:stripped(lastclause) !~? '^\%(SEARCH\|EVALUATE\|READ\)\>'
168            let ind = indent(lastclause)
169        elseif lastclause > 0
170            let ind = indent(lastclause) + shiftwidth()
171        endif
172    elseif line =~? '^WHEN\>'
173        let ind = ind + shiftwidth()
174    endif
175    "I'm not sure why I had this
176    "if line =~? '^ELSE\>-\@!' && line !~? '\.$'
177        "let ind = indent(s:prevgood(lnum))
178    "endif
179    if cline =~? '^\(END\)\>-\@!'
180        " On lines with just END, 'guess' a simple shift left
181        let ind = ind - shiftwidth()
182    elseif cline =~? '^\(END-IF\|THEN\|ELSE\)\>-\@!'
183        call cursor(a:lnum,indent(a:lnum))
184        let match = searchpair('\c-\@<!\<IF\>','\c-\@<!\%(THEN\|ELSE\)\>','\c-\@<!\<END-IF\>\zs','bnW',s:skip)
185        if match > 0
186            let ind = indent(match)
187        endif
188    elseif cline =~? '^END-[A-Z]'
189        let beginword = matchstr(cline,'\c\<END-\zs[A-Z0-9-]\+')
190        let endword = 'END-'.beginword
191        let first = 0
192        let suffix = '.*\%(\n\%(\%(\s*\|.\{6\}\)[*/].*\n\)*\)\=\s*'
193        if beginword =~? '^\%(ADD\|COMPUTE\|DIVIDE\|MULTIPLY\|SUBTRACT\)$'
194            let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=ON\s\+SIZE\s\+ERROR'
195            let g:beginword = beginword
196            let first = 1
197        elseif beginword =~? '^\%(STRING\|UNSTRING\)$'
198            let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=ON\s\+OVERFLOW'
199            let first = 1
200        elseif beginword =~? '^\%(ACCEPT\|DISPLAY\)$'
201            let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=ON\s\+EXCEPTION'
202            let first = 1
203        elseif beginword ==? 'CALL'
204            let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=ON\s\+\%(EXCEPTION\|OVERFLOW\)'
205            let first = 1
206        elseif beginword =~? '^\%(DELETE\|REWRITE\|START\|READ\|WRITE\)$'
207            let first = 1
208            let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=\(INVALID\s\+KEY'
209            if beginword =~? '^READ'
210                let first = 0
211                let beginword = beginword . '\|AT\s\+END\|NO\s\+DATA'
212            elseif beginword =~? '^WRITE'
213                let beginword = beginword . '\|AT\s\+END-OF-PAGE'
214            endif
215            let beginword = beginword . '\)'
216        endif
217        call cursor(a:lnum,indent(a:lnum))
218        let match = searchpair('\c-\@<!\<'.beginword.'\>','','\c\<'.endword.'\>\zs','bnW'.(first? 'r' : ''),s:skip)
219        if match > 0
220            let ind = indent(match)
221        elseif cline =~? '^\(END-\(READ\|EVALUATE\|SEARCH\|PERFORM\)\)\>'
222            let ind = ind - shiftwidth()
223        endif
224    endif
225    return ind < bshft ? bshft : ind
226endfunction
227