Entab And Detab
May 6, 2011
In ancient times, say the 1970s, religious wars were fought about whether to indent blocks of code using tabs or spaces, and how wide the indents should be. Then editing environments got better and programmers stopped arguing about tabs and started arguing about other things, like where the braces go. Now, with the advent of the internet, the problem of tab/space indentation seems to be returning, as copy/paste operations between web browsers and text editors seem to get the indentation wrong (especially when the source is the evil PDF format).
In ancient times, the normal solution to the tab/space indentation problem was a pair of programs, entab and detab, that could easily convert between the two formats. Now, with the internet, it behooves us to resurrect those old programs.
Your task is to write programs that convert files using tabs for indentation to files using spaces for indentation, and vice versa; be sure to permit an argument specifying the width of the tab. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
My Haskell solution (see http://bonsaicode.wordpress.com/2011/05/06/programming-praxis-entab-and-detab/ for a version with comments):
Remko: you change all tabs/spaces. You should only consider the ones at the head of a line. And they may be mixed.
“Remco”, sorry for the spelling error.
Axio: Correct, detab changes all tabs, since this is the behaviour of the provided solution. entab only process spaces at the start of the line. Mixed spaces and tabs are already handled correctly.
Gambit-C Scheme, and some macros inspired by Common Lisp…
Not the most beautiful code, and no magic involved.
Will handle mixed tabs and spaces on same line, and stop at the first non-space-nor-tab character.
Procedures to apply to each line of a loaded file.
I think that’s pretty much it…
(define *tab-width* 4)
(define (flush seen)
(unless (zero? seen)
(for-each (lambda (x) (display " ")) (iota 1 seen))))
(define (entab-line line #!optional (tab-width *tab-width*))
(let ((sl (string-length line)))
(let loop ((pos 0)
(seen 0))
(if (= pos sl)
(flush seen)
(case (string-ref line pos)
((#\space)
(if (= seen (- *tab-width* 1))
(begin
(display #\tab)
(loop (1+ pos)
0))
(loop (1+ pos)
(1+ seen))))
((#\tab)
(flush seen)
(display #\tab)
(loop (1+ pos)
0))
(else
(flush seen)
(display (substring line pos sl))))))))
(define (detab-line line #!optional (tab-width *tab-width*))
(let ((sl (string-length line)))
(let loop ((pos 0))
(unless (= pos sl)
(case (string-ref line pos)
((#\space)
(display " ")
(loop (1+ pos)))
((#\tab)
(flush tab-width)
(loop (1+ pos)))
(else
(display (substring line pos sl))))))))
With better indentation, hopefully.
(define *tab-width* 4)
;
(define (flush seen)
(unless (zero? seen)
(for-each (lambda (x) (display " ")) (iota 1 seen))))
;
(define (entab-line line #!optional (tab-width *tab-width*))
(let ((sl (string-length line)))
(let loop ((pos 0)
(seen 0))
(if (= pos sl)
(flush seen)
(case (string-ref line pos)
((#\space)
(if (= seen (- *tab-width* 1))
(begin
(display #\tab)
(loop (1+ pos)
0))
(loop (1+ pos)
(1+ seen))))
((#\tab)
(flush seen)
(display #\tab)
(loop (1+ pos)
0))
(else
(flush seen)
(display (substring line pos sl))))))))
;
(define (detab-line line #!optional (tab-width *tab-width*))
(let ((sl (string-length line)))
(let loop ((pos 0))
(unless (= pos sl)
(case (string-ref line pos)
((#\space)
(display " ")
(loop (1+ pos)))
((#\tab)
(flush tab-width)
(loop (1+ pos)))
(else
(display (substring line pos sl))))))))
My solution in C:
[…] – entab and detab are used to handle problems on copy-and-paste from text files (ref) […]
Write a program detab that replaces tabs in the input with the proper number of blanks to space to the next tab stop. Assume a fixed set of tab stops, say every n columns. Should n be a variable or a symbolic parameter?