#!/bin/ruby # format Karel programs using the assumption that each token # is separated by at least one space from all other tokens #---------------------------------------------------------------------- # Language to process: # # -> { } # -> define # -> '{' { } '}' # -> # -> # -> # -> # -> ';' # -> if '(' ')' [ else ] # -> # while '(' ')' # -> # (move | turnleft | dropbeeper | takebeeper | ) ';' # -> OnBeeper | FrontIsClear | FrontIsBlocked # def report_error(message, rest_of_code) abort "Error: #{message}\n -- remaining code: #{rest_of_code.join(' ')}" end def check_for(token, code) if code[0] != token report_error("Expected #{token}, got #{code[0]}", code[1..-1]) end code.shift end def indentation(nesting_level) ' ' * (2 * nesting_level) end def pp_program(code, depth) while !code.empty? pp_definition(code, depth) end end def pp_definition(code, depth) check_for('define', code) name = code.shift puts "#{indentation(depth)}define #{name}" pp_compound_instruction(code, depth + 1) puts "" end # assumes depth set for simple instructions; outdents the braces # so they are indented with context instead def pp_compound_instruction(code, depth) check_for('{', code) puts "#{indentation(depth - 1)}{" while !code.empty? && code[0] != '}' pp_instruction(code, depth) end check_for('}', code) puts "#{indentation(depth - 1)}}" end def pp_instruction(code, depth) case code[0] when '{' pp_compound_instruction(code, depth) when 'if' pp_if_instruction(code, depth) when 'while' pp_while_instruction(code, depth) when ';' puts "#{indentation(depth)};" else pp_simple_instruction(code, depth) end end def pp_if_instruction(code, depth) check_for('if', code) check_for('(', code) print "#{indentation(depth)}if ( " pp_test(code, depth) check_for(')', code) puts ' )' pp_instruction(code, depth + 1) if code[0] == 'else' code.shift puts "#{indentation(depth)}else" pp_instruction(code, depth + 1) end end def pp_while_instruction(code, depth) check_for('while', code) check_for('(', code) print "#{indentation(depth)}while ( " pp_test(code, depth) check_for(')', code) puts ' )' pp_instruction(code, depth + 1) end def pp_simple_instruction(code, depth) print(indentation(depth)) #puts ">> Looking at #{code[0]} << " # move, turnleft, dropbeeper, takebeeper: predefined instructions # identifier: user-defined instruction cmd = code.shift print cmd check_for(';', code) puts ' ;' end def pp_test(code, depth) if !['OnBeeper', 'FrontIsClear', 'FrontIsBlocked'].include?(code[0]) report_error("Test expected", code) end print(code[0]) code.shift end # input: Array(String) def process(lines) lines.each { |line| line.chomp! } all_text = lines.join(' ').split pp_program(all_text, 0) end def main if ARGV[0] && !ARGV[0].empty? lines = IO.readlines(ARGV[0]) else lines = $stdin.readlines end process(lines) end if __FILE__ == $0 if false process(['define main { turnleft ; move ; }']) process(['define main { if ( OnBeeper ) turnleft ; ', 'else { move ; move ; } }']) process(['define main { if ( OnBeeper ) { ', 'if ( FrontIsClear ) turnleft ; else { move ; move ; } } }']) else main end end