Skip to content

Instantly share code, notes, and snippets.

@rtweeks
Last active December 4, 2015 18:46
Show Gist options
  • Save rtweeks/11346958 to your computer and use it in GitHub Desktop.
Save rtweeks/11346958 to your computer and use it in GitHub Desktop.
Reformat Visual Basic 6 (VB6) code to use consistent indentation and line widths.
#!/usr/bin/env ruby
# The MIT License (MIT)
#
# Copyright (c) 2014 Richard T. Weeks
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Reformat Visual Basic 6 or VBScript code to use consistent indentation and
# line widths. Main class is VB::Sanitizer.
require 'optparse'
require 'ostruct'
require 'stringio'
module VB
TOKEN_MATCHER = /^(
\s+ | # Whitespace
<% | %> | # ASP processing tag
[-+*\/\\^&=,.<>()_]+ | # Operator
"([^"]|"")*" | # String literal
\#[^\#]*\# | # Date-time literal
'.* | # Comment
(\d+(.\d*)?|.\d+)(E\d+)?[D@R\#I%L&SF!]? | # Number
[A-Z_][A-Z0-9_]* # Identifier
)/ixo
def self.parse_options(argv)
options = OpenStruct.new
optparser = OptionParser.new do |flags|
flags.banner = [
"Usage: #{File.basename($0)} [<options>]"
].join("\n")
flags.separator ''
options.indent = ' '
flags.on('-sN', '--indent-size=N', "Set indent size (spaces)") do |n|
options.indent = ' ' * n.to_i
end
flags.on('--tab-indent', "Use a tab for each level of indentation") do
options.indent = "\t"
end
options.dos_newlines = false
flags.on('-d', '--dos-newlines', "Use DOS-style CRLF newlines") do
options.dos_newlines = true
end
flags.on('--inplace', "Write output back to input file") do
options.inplace = true
end
end
argv = optparser.parse(argv)
return argv, options
end
def self.tokenize(line)
remainder = line
tokens = []
until remainder.empty?
if remainder =~ TOKEN_MATCHER
tokens << $&
remainder = remainder[$&.length..-1]
else
tokens << remainder
break
end
end
return tokens
rescue
$stderr.puts "Error while tokenizing line: " + line
$stderr.puts " (unparsed segment: #{remainder})"
raise
end
def self.statement_block_role(tokens)
tokens = tokens.dup
# Ignore whitespace
tokens.reject! {|t| t =~ /^\s+$/}
# Ignore trailing comments
if /^'/ === tokens[-1]
tokens.pop
end
tokens.map! {|t| t.downcase}
return :start if %w[begin while do for with class].include? tokens[0]
if tokens[0] == 'if'
last_token = tokens[-1]
last_token = tokens[-2] if last_token.start_with? "'"
return :start if last_token == 'then'
return :statement
end
return :start if tokens[0..1] == ['select', 'case']
if tokens[0..2].any? {|t| %w[function sub property].include? t}
i = 0
i += 1 if %w[private public friend].include? tokens[i]
i += 1 if 'static' == tokens[i]
return :start if %w[function sub property].include? tokens[i]
end
return :separator if %w[elseif else case].include? tokens[0]
return :end if %w[end wend next].include? tokens[0]
return :statement
end
class Sanitizer
def initialize(**options)
@indent = options[:indent] || ' '
@gutter = options[:gutter] || 90
end
def sanitize(lines, &blk)
return enum_for(:sanitize, lines) unless block_given?
indent_level = 0
previous_line_empty = false
continued_tokens = []
lines.each do |l|
tokens, continued_tokens = continued_tokens + VB.tokenize(l.chomp), []
# Drop any leading whitespace (we'll handle all indentation)
if /^\s*$/ === tokens[0]
tokens.shift
end
if tokens.empty?
next if previous_line_empty
previous_line_empty = true
else
previous_line_empty = false
end
if tokens[-1] == '_'
continued_tokens = tokens[0..-2]
next
end
case VB.statement_block_role(tokens)
when :start
output(tokens, indent_level, &blk)
indent_level += 1
when :separator
indent_level -= 1
output(tokens, indent_level, &blk)
indent_level += 1
when :end
indent_level -= 1
output(tokens, indent_level, &blk)
else
output(tokens, indent_level, &blk)
end
end
end
def output(tokens, indent_level)
line = (@indent * indent_level)
split_point = nil
tokens.each do |tok|
if split_point.nil? or /^('|\s+$)/ === tok or (line.length + tok.length <= @gutter)
line += tok
else
yield(line[0...split_point] + " _")
line = (@indent * (indent_level + 2)) + line[split_point..-1].lstrip + tok
split_point = nil
end
if tok =~ /^(and|or|&|\+)$/io
# Mark this spot for line split
split_point = line.length
end
end
yield(line) if tokens.empty? or not line.strip.empty?
end
end
end
if $0 == __FILE__
argv, options = VB::parse_options(ARGV)
tool = VB::Sanitizer.new(indent: options.indent)
newline = options.dos_newlines ? "\r\n" : "\n"
out = options.inplace ? StringIO.new : $stdout
open argv[0], 'r' do |input|
tool.sanitize input.each_line do |line|
out.print(line + newline)
end
end
if options.inplace
open argv[0], 'w' do |outfile|
outfile.write(out.string)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment