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