# sedcheck.sed - detects various POSIX compatibility issues in sed scripts
# 
# (C) 2003 Laurent Vogel - GPL version 2 or later at your option.
# 
# 2003-09-12 version 0.1 

# hide _,<,>,~ behind '
s/['_<>~]/&'a_b<c>d~e,/g
s/\(['_<>~]\)[^,]*\1\([^,]\)[^,]*,/'\2/g

# consider the hold buffer; initialize line number and issue number
x  
1 s/.*/C~0,0+012345678910999000990090/
# increment line number
s/\(.\)\(9*\)\(,[^+]*+[^9]*\1\(.0*\).*\2\(0*\)\)/\4\5\3/
x  

# get back state from previous line
G  
s/\(.*\)\n\([^~]*~\).*/_\2\1/

:loop

# outside commands: _~ = at top level, _{{~ inside two nested groups
s/\(_{*~\)\([ 	][ 	]*\)/<trailing 'blank's should be avoided>\2\1/
s/_\({{*~\);/<";" not allowed in {...} groups>;_C\1/
s/_~;/;_C~/
s/_\({*~\)}/_c\1}/
s/\(_{*~\)\(#.*\)/<"#" should be preceded by ";" or 'newline'>\2\1/
s/_\({*~\)$/_C\1/
s/\(_{*~\)\(.*\)/<trailing garbage after command (rest of the line \
ignored)>\2\1/

# 'C'ommand: skip blanks, try first address
s/\(_C[^~]*~\)\([ 	][ 	]*\)/\2\1/
s/_C\([^~]*~[0-9$/\]\)/_ad\1/

# 'a'ddress
s/\(_a[^~]*~\)\(00*\)\([0-9]\)/<useless leading 0>\2\1\3/
s/_a\([^~]*~\)0/<line 0 is a GNU extension>0_\1/
s/_a\([^~]*~\)\\$/\\<'newline' is not a valid delimiter>_\\b'z\1/
s/_a\([^~]*~\)\\\(\\[^\]*\\\)/\\<"\\" is not a valid delimiter>\2_\1/
s/_a[^{~]*\({*~\)\\\(\\.*\)/\\<"\\" is not a valid delimiter>\2<missing \
delimiter>_\1/
s/_a\([^~]*~\)\\\('*[^']\)/\\\2_b\2\1/
s/_a\([^~]*~\)\//\/_b\/\1/
s/_a\([^~]*~\)\$/$_\1/
s/_a\([^~]*~\)\([1-9][0-9]*\)/\2_\1/
s/_a.\([^~]*~\)\(.*\)/<bad address (rest of line ignored)>\2_\1/

# 'd' is 1addr command, 'e' is 2addr command: try second address
s/\(_d[^~]*~\)\([ 	][ 	]*\),/<no 'blank's allowed>\2\1,/
s/_d\([^~]*~\),\([ 	][ 	]*\)/,<no 'blank's allowed>\2_ae\1/
s/_d\([^~]*~\),/,_ae\1/

# a function can be preceded by blanks and '!'s 
s/\(_[cde][^~]*~\)\([ 	][ 	]*\)/\2\1/
s/\(_[Cde][^~]*~\)!\(!*\)!/!<multiple "!" not recommended>\2\1!/
s/\(_[Cde][^~]*~\)!\([ 	][ 	]*\)/!<no 'blank's allowed after "!">\2\1/
s/\(_[Cde][^~]*~\)!/!\1/
s/_C\([^~]*~.\)/_c\1/

# 0addr and 1 addr messages 
s/_[de]\([^~]*~\)$/<missing command>_\1/
s/_[de]\([^~]*~[:#}]\)/<no addresses allowed>_c\1/
s/_e\([^~]*~[aiqr=]\)/<at most one address allowed>_c\1/

# no need to remember the number of addresses any longer
s/_[de]/_c/

# comment, empty command
s/_c\([^~]*~\)\(#.*\)/\2_\1/
s/_c\([^~]*~\);/_\1;/

# {, }
s/_c\([^~]*~\){/{_C{\1/
s/_c\([^~]*~\){/{_C{\1/
s/^\([ 	]*[^ 	_][^_]*\)_c{\([^~]*~\)}/\1<"}" should be preceded by a \
'newline'>}_}\2/
s/_c{\([^~]*~\)}/}_}\1/
s/_}\([^~]*~\)\([ 	]*\);/\2<";" not allowed after { ... } groups>;_C\1/
s/_}\([^~]*~\)\([ 	]*\)/\2_\1/
s/_c~}/<mismatched { ... }>}_~/

# a\, i\, c\
s/_c\([^~]*~\)\([aic]\\\)\([ 	][ 	]*\)$/\2<trailing 'blank's>\3_\\t\1/
s/_c\([^~]*~\)\([aic]\\\)$/\2_\\t\1/
s/_c\([^~]*~\)\([aic]\\\)/\2<'newline' expected>_t\1/
s/_c\([^~]*~\)\([aic]\)/\2<"\\'newline'" expected>_t\1/

s/\(_t[^~]*~\)\([^\][^\]*\)/\2\1/
s/_\(t[^~]*~\)\\$/\\_\\\1/
s/\(_t[^~]*~\)\\\\/\\\\\1/
s/\(_t[^~]*~\)\(\\'*[^\']\)/<useless "\\">\2\1/
s/_t\([^~]*~\)$/_\1/

# b, t, : - POSIX is quite unclear: are leading spaces and tabs allowed?
# I assume here that zero or one leading space is OK, and anything else
# doubtful.
s/_c\([^~]*~\):\([ 	]*[;#}]\)/:<missing label>_\1\2/
s/_c\([^~]*~\):\([ 	]*\)$/:<missing label>_\1\2/
s/_c\([^~]*~\):\([ 	][ 	]*\)/:<'blank's not recommended here>\2_l\1/
s/_c\([^~]*~\):/:_l\1/

s/_c\([^~]*~\)\([bt]\)\([ 	]*\)$/\2_\1\3/
s/_c\([^~]*~\)\([bt]\) /\2 _l\1/
s/_c\([^~]*~\)\([bt]\)\([ 	]*\)/\2<single 'space' recommended here>\3_l\1/

s/_l\([^~]*~\)\([^;#} 	]*\)\([;}#]\)/\2<avoid "\3" in labels>_\1\3/
s/_l\([^~]*~\)\([^;#} 	]*\)\([ 	]\)/\2<avoid 'blank's in labels>\3_C\1/
s/_l\([^~]*~\)\('*[^']'*[^']'*[^']'*[^']'*[^']'*[^']'*[^']'*[^']\)\(..*\)/\2<\
label more than 8 characters long>\3_\1/
s/_l\([^~]*~\)\(.*\)\([ 	][ 	]*\)$/\2<trailing 'blank's not \
recommended>\3_\1/
s/_l\([^~]*~\)\(.*\)/\2_\1/

# d, D, g, G, h, H, l, n, N, p, P, q, x, =
s/_c\([^~]*~\)\([dDgGhHlnNpPqx=]\)/\2_\1/

# r, w
s/_c\([^~]*~\)\([rw]\)/\2_n\1/
s/_n\([^~]*~\)\([ 	][ 	]*\)/\2_m\1/
s/_n\([^~]*~\)\(..*\)/<'blank's expected>\2_\1/
s/_m\([^~]*~\)$/<missing filename>_\1/
s/_m\([^~]*~\)\([^;#]*\)\([;#].*\)/\2<part of the filename>\3_\1/
s/_m\([^~]*~\)\(..*\)/\2_\1/

# s, y
s/_c\([^~]*~\)s$/s<'newline' is not a valid delimiter>_\\b'zs'zS0\1/
s/_c\([^~]*~\)y$/y<'newline' is not a valid delimiter>_\\y'zy'y\1/
s/_c\([^~]*~\)s\(\\[^\]*\\[^\]*\\\)/s<"\\" is not a valid delimiter>\2_S0\1/
s/_c\([^~]*~\)y\(\\[^\]*\\[^\]*\\\)/y<"\\" is not a valid delimiter>\2_\1/
s/_c\([^~]*~\)\([sy]\)\(\\.*\)/\2<"\\" is not a valid delimiter>\3<missing \
delimiter>_\1/
s/_c\([^~]*~\)s\('*[^']\)/s\2_b\2s\2S0\1/
s/_c\([^~]*~\)y\('*[^']\)/y\2_y\2y\2\1/

# s right hand side and both sides of y
:sy
s/\(_[sy]\/[^~]*~\)\([^\/][^\/]*\)/\2\1/
s/_[sy]\('*[^']\)\([^~]*~\)\1/\1_\2/
s/_\([sy][^~]*~\)\([^\]\)/\2_?\1/
s/_\(s\([1-9&]\)[^~]*~\)\\\2/<"\\\2" ambiguous with "\2" as delimiter\
>\\\2_?\1/
s/_\([sy]\('*[^']\)[^~]*~\)\\\2/\\\2_?\1/
s/_\(s[^~]*~\)\(\\[^1-9&n\]\)/<"\2" unspecified>\2_?\1/
s/_\(s[^~]*~\)\(\\n\)/<"\\n" unspecified (use 'newline' instead)>\2_?\1/
s/_\(y[^~]*~\)\(\\[^n\]\)/<"\2" unspecified>\2_?\1/
s/_\([sy][^~]*~\)\(\\'*.\)/\2_?\1/
s/_\(s[^~]*~\)\\$/\\_\\\1/
s/_\(y[^~]*~\)\\$/<"\\'newline'" invalid (use "\\n" instead)>\\_\\\1/
s/_y'y\([^~]*~\)$/_\1/
s/_[sy]'z\([^~]*~\)$/_\\\1/
s/_y'*[^']y'*[^']\([^~]*~\)$/<missing delimiter>_\1/
s/_[sy]'*[^']\([^~]*~\)$/<missing delimiter>_\1/
/_[sy]/ t sy

# s flags
s/\(_S[^~]*~\)p/p\1/
s/_S[01]\([^~]*~\)g/g_S1\1/
s/_S0\([^~]*~\)\([0-9][0-9]*\)/\2_S2\1/
s/\(_S1[^~]*~\)\([0-9][0-9]*\)/<both "g" and "n" flags>\2\1/
s/\(_S2[^~]*~\)\([0-9][0-9]*\)/<multiple "n" flags>\2\1/
s/\(_S2[^~]*~\)g/<both "g" and "n" flags>g\1/
s/_S.\([^~]*~[ 	;#]\)/_\1/
s/_S.\([^~]*~\)$/_\1/
s/_S.\([^~]*~\)\([^pg0-9w]\)$/<unknown flag>\2_\1/
s/_S.\([^~]*~\)\([^pg0-9w].*\)/<unknown flag (rest of line ignored)>\2_\1/
s/_S.\([^~]*~\)w/w_n\1/

# #
s/_c\([^~]*~\)\(#.*\)/\2_\1/

s/_c\([^~]*~\)\([^#; 	].*\)/<unknown command (rest of line ignored)>\2_\1/

# 'b'eginning of regexp
# "*" is special unless at the beginning of the regex (after optional "^")
s/_b\([^^][^~]*~\)^/^_b\1/
s/_b\([^*][^~]*~\)\*/*_r\1/
s/_b/_r/

# 'r'egexp loop. state 'R' after a repeated symbol and 'B' inside a bracket
# expression. 
/_[rB]/ {

    :reloop
    # shortcut, when the delimiter is "/"
    s/_[rR]\(\/[^~]*~\)\([^\/*$[][^\/*$[]*\)/\2_r\1/
    s/_[rR]\('*[^']\)\([^~]*~\)\1/\1_\2/

    # \x
    s/_[rR]\(\([n.(){1-9^$*[]\)[^~]*~\)\\\2/<"\\\2" ambiguous with "\2" as \
delimiter>_?r\1/
    s/_[rR]\(\('*[^']\)[^~]*~\)\\\2/\\\2_?r\1/
    s/_[rR]\([^~]*~\)\(\\[^n.\*^$(){1-9?+[]\)/<"\2" undefined>\2_?r\1/
    s/_[rR]\([^~]*~\)\\?/<"\\?" undefined (use "\\{0,1\\}" instead)>\\?_?r\1/
    s/_[rR]\([^~]*~\)\\+/<"\\+" undefined (use "\\{1,\\}" instead)>\\+_?r\1/
    s/_[rR]\([^~]*~\)\(\\[n.\*^$1-9)[]\)/\2_?r\1/
    s/_[rR]\([^~]*~\)\\(\^/\\(<subexpression anchoring is optional (use \
"\\^" instead)>^_?r\1/
    s/_[rR]\([^~]*~\)\\(\*/\\(*_?r\1/
    s/_[rR]\([^~]*~\)\\(/\\(_?r\1/

    # \{...\}
    s/_[rR]\([^~]*~\)\(\\{\)\([^0-9]\)/\2<bad interval>\3_?r\1/
    s/_[rR]\([^~]*~\)\(\\{[0-9][0-9]*\)\([^\,0-9]\)/\2<bad interval>\3_?r\1/
    s/_[rR]\([^~]*~\)\(\\{[0-9][0-9]*\)\(\\[^}]\)/\2<bad interval>\3_?r\1/
    s/_[rR]\([^~]*~\)\(\\{[0-9][0-9]*,\)\([^\0-9]\)/\2<bad interval>\3_?r\1/
    s/_[rR]\([^~]*~\)\(\\{[0-9][0-9]*,[0-9][0-9]*\)\([^\0-9]\)/\2<bad \
interval>\3_?r\1/
    s/_[rR]\([^~]*~\)\(\\{[0-9][0-9]*,[0-9][0-9]*\)\(\\[^}]\)/\2<bad \
interval>\3_?r\1/
    s/_R\([^~]*~\)\(\\{[^}]*}\)/<multiple "*" or intervals>\2_?r\1/
    s/_r\([^~]*~\)\(\\{[^}]*}\)/\2_?R\1/

    # entering a bracket expression
    s/_[rR]\([^~]*~\)\(\[^^]\)/\2_B\1/
    s/_[rR]\([^~]*~\)\(\[^]\)/\2_B\1/
    s/_[rR]\([^~]*~\)\(\[^\)/\2_B\1/
    s/_[rR]\([^~]*~\)\(\[\)/\2_B\1/

    # bracket expression
    s/\(_B[^~]*~\)\([^]\[][^]\[]*\)/\2\1/
    s/\(_B[^~]*~\)\\n/<"\\n" ambiguous (use "n\\" instead)>\\n\1/
    s/\(_B[^~]*~\)\\\([^n]\)/\\\1\2/
    s/_\(B[^~]*~\)\\$/<"\\'newline'" not allowed>\\_\\\1/
    s/\(_B[^~]*~\)\(\[[.=:].[^]]*]\)/\2\1/
    s/\(_B[^~]*~\)\[\([^.=:]\)/[\1\2/
    s/_B\([^~]*~\)]/]_?r\1/

    # *
    s/_R\([^~]*~\)\*/<multiple "*" or intervals>*_?r\1/
    s/_r\([^~]*~\)\*/*_?R\1/

    # $
    s/_[rR]\([^~]*~\)\(\$\\)\)/<subexpression anchoring is optional (use \
"\\$" instead)>\2_?r\1/
    s/_[rR]\([^~]*~\)\$/$_?r\1/

    # \<newline>
    s/_[rR]'z\([^~]*~\)$/_\\\1/
    s/_[rRB][^{~]*\({*~\)$/<missing delimiter>_\1/
    s/_[rR]\([^~]*~\)\\$/<"\\'newline'" not allowed (use "\\n" \
instead)>\\_\\r\1/

    # any other character
    s/_[rR]\([^~]*~\)\(.\)/\2_?r\1/

    s/_?/_/
    /_[rRB]/ t reloop
}  

# force re-cycle
s/_?/_/
t loop

s/\n//g

# end of line not reached by the parser?
s/_\([^~]*~\)\(..*\)/<syntax error (rest of line ignored)>\2_C~/

# update state in the hold buffer, removing any leading \ in the state
# (these are used to control when a command can extend to the next line) 
G  
s/\([^_]*\)_\\*\(.*\)\(\n\)[^~]*~\(.*\)/\2\4\3\1/
h  
/</ !b noerror

:error
s/[^~]*~\([0-9]*\)[^<]*<\([^>]*\).*/line \1: \2/
s/'\([^']*\)'/<\1>/g
p  
g  
s/.*\n//
s/<[^>]*>//g
s/'./&'a_b<c>d~e,/g
s/'\(.\)[^,]*\([^,]\)\1[^,]*,/\2/g
p  
g  
s/.*\n//
s/\([^<]*\)<.*/>\1/
:tospace
s/>'*[^	]/ >/
s/>\(		*\)/\1>/
s/>$/^/p
t tospace
g  
# remove first issue and increment issue count
s/<[^>]*>//
s/\(.\)\(9*\)\(+[0-8]*\1\([0-9]0*\)[0-9]*\2\(0*\).*\n\)/\4\5\3/
h  
/</ b error

:noerror
# remove the line from the future hold buffer
s/\n.*//
h  

$ !d

# if the issue count is not null, report it.
/[^~]*~[^,]*,\([1-9][0-9]*\).*/ {
    s//\1 issues reported./
    s/^\(1 issue\)s/\1/
    q
}  
d  



### colorized by sedsed, a debugger and code formatter for sed scripts
### original script: http://lvogel.free.fr/sed/sedcheck.sed