| 1 | #!/usr/bin/env ruby |
|---|
| 2 | # encoding: utf-8 |
|---|
| 3 | |
|---|
| 4 | RUBY_VERSION =~ /\A(1\.9|2\.)/ or raise "#{$0} needs ruby 1.9. See README." |
|---|
| 5 | |
|---|
| 6 | |
|---|
| 7 | class STDRedRun |
|---|
| 8 | def print_usage_and_exit(msg=nil,code=2) |
|---|
| 9 | msg += "\n\n" if msg |
|---|
| 10 | msg ||= "" |
|---|
| 11 | STDERR.print <<END |
|---|
| 12 | #{msg}#{$0} [-q] [--format=FORMAT] [ --start=RULE ] [ FILENAME | -e CODE ] |
|---|
| 13 | |
|---|
| 14 | If the environment variable STD_RED_CACHEDIR is set, output will be |
|---|
| 15 | saved there, and used in preference to reparsing, when the same code |
|---|
| 16 | is seen again. -e CODE is not cached. And when std.rb is changed, |
|---|
| 17 | previous cache entries are ignored. |
|---|
| 18 | |
|---|
| 19 | FORMAT |
|---|
| 20 | p5a # Dump format for perl5. |
|---|
| 21 | cl # Dump format for Common Lisp. |
|---|
| 22 | yaml # depreciated |
|---|
| 23 | |
|---|
| 24 | Other arguments: |
|---|
| 25 | |
|---|
| 26 | --error-message=MSG |
|---|
| 27 | What to say when the parse fails. |
|---|
| 28 | |
|---|
| 29 | --at=POS |
|---|
| 30 | Start parsing at position POS, instead of at 0. |
|---|
| 31 | Implies that a partial parse is ok. |
|---|
| 32 | A speculative feature provided to aid the development of other |
|---|
| 33 | parsers, by permitting them to delegate some parsing tasks. |
|---|
| 34 | |
|---|
| 35 | END |
|---|
| 36 | exit(code) |
|---|
| 37 | end |
|---|
| 38 | |
|---|
| 39 | def main |
|---|
| 40 | start = '_UNIT'; at = 0 |
|---|
| 41 | quiet = format = false |
|---|
| 42 | code = nil |
|---|
| 43 | error_message = "" |
|---|
| 44 | if ARGV.empty? |
|---|
| 45 | print "\nRun #{$0} --help to get help.\n"; |
|---|
| 46 | $:.push(File.dirname($0)) |
|---|
| 47 | require 'std' |
|---|
| 48 | Repl.new.parser_rule |
|---|
| 49 | exit(0) |
|---|
| 50 | end |
|---|
| 51 | if ARGV.size == 1 and ARGV[0] == '--help' |
|---|
| 52 | print_usage_and_exit(nil,0) |
|---|
| 53 | end |
|---|
| 54 | if m = ARGV[0].match(/--error-message=(.+)/) |
|---|
| 55 | ARGV.shift |
|---|
| 56 | error_message = m[1]+"\n" |
|---|
| 57 | end |
|---|
| 58 | if ARGV[0] == '-q' |
|---|
| 59 | ARGV.shift |
|---|
| 60 | quiet = true |
|---|
| 61 | end |
|---|
| 62 | if m = ARGV[0].match(/--format=(\w+)/) |
|---|
| 63 | ARGV.shift |
|---|
| 64 | format = m[1] |
|---|
| 65 | end |
|---|
| 66 | if m = ARGV[0].match(/--start=(\w+)/) |
|---|
| 67 | ARGV.shift |
|---|
| 68 | start = m[1] |
|---|
| 69 | end |
|---|
| 70 | if m = ARGV[0].match(/--at=(\d+)/) |
|---|
| 71 | ARGV.shift |
|---|
| 72 | at = m[1].to_i |
|---|
| 73 | end |
|---|
| 74 | if ARGV[0] == '-e' |
|---|
| 75 | ARGV.shift |
|---|
| 76 | code = ARGV.shift |
|---|
| 77 | code = code.dup.force_encoding('utf-8') |
|---|
| 78 | elsif not ARGV.empty? |
|---|
| 79 | filename = ARGV.shift |
|---|
| 80 | print_usage_and_exit("File #{filename} doesn't exist.") if not File.exists?(filename) |
|---|
| 81 | code = File.open(filename,'r:utf-8'){|f|f.read} |
|---|
| 82 | else |
|---|
| 83 | print_usage_and_exit |
|---|
| 84 | end |
|---|
| 85 | |
|---|
| 86 | $quiet = quiet |
|---|
| 87 | $:.push(File.dirname($0)) |
|---|
| 88 | require 'std' |
|---|
| 89 | |
|---|
| 90 | output = cached_output_for(code, format ? format : nil) |
|---|
| 91 | if output; print output; exit end |
|---|
| 92 | |
|---|
| 93 | # Hacks |
|---|
| 94 | whiteout = ->(s){s.gsub(/[^ \n]/,' ')}; |
|---|
| 95 | #code = code.gsub(/\n=begin.*?\n=end[^\n]*/um) {|m|whiteout.(m)} |
|---|
| 96 | code = code.gsub(/\n=kwid.*?\n=cut[^\n]*/um) {|m|whiteout.(m)} |
|---|
| 97 | code = code.gsub(/\n=pod.*?\n=cut[^\n]*/um) {|m|whiteout.(m)} |
|---|
| 98 | code = code.gsub(/\n=head1.*?\n=cut[^\n]*/um) {|m|whiteout.(m)} |
|---|
| 99 | |
|---|
| 100 | kibitz = false |
|---|
| 101 | pn = Perl.new(code,at) |
|---|
| 102 | tree=nil |
|---|
| 103 | STDERR.print "parsing...\n" if kibitz |
|---|
| 104 | begin |
|---|
| 105 | $env_vars.scope_enter(:unitstopper) |
|---|
| 106 | $env_vars[:unitstopper] = "_EOS" |
|---|
| 107 | $env_vars.scope_enter(:stop) |
|---|
| 108 | $env_vars[:stop] = "if you see this dont stop" |
|---|
| 109 | tree = pn.send(start.to_sym) |
|---|
| 110 | rescue RuntimeError => e |
|---|
| 111 | STDERR.print error_message, e.message |
|---|
| 112 | bt = e.backtrace |
|---|
| 113 | bt = bt.slice(0,10) if quiet |
|---|
| 114 | s = " "+bt.join("\n ")+"\n" |
|---|
| 115 | if m = e.backtrace[0].match('^(.*?)STD_red') |
|---|
| 116 | s = s.gsub(m[1],'') |
|---|
| 117 | end |
|---|
| 118 | STDERR.print s |
|---|
| 119 | exit(1) |
|---|
| 120 | rescue Interrupt |
|---|
| 121 | exit(1) |
|---|
| 122 | end |
|---|
| 123 | if not tree |
|---|
| 124 | STDERR.print "Parse failed.\n" |
|---|
| 125 | exit(1) |
|---|
| 126 | end |
|---|
| 127 | if not format |
|---|
| 128 | STDERR.print "describing...\n" if kibitz |
|---|
| 129 | print tree.match_describe |
|---|
| 130 | elsif format == 'p5a' |
|---|
| 131 | print out(tree.to_dump0) |
|---|
| 132 | elsif format == 'cl' |
|---|
| 133 | print out(tree.to_dump1) |
|---|
| 134 | elsif format == 'yaml' |
|---|
| 135 | require "yaml" |
|---|
| 136 | tree.prepare_for_yaml_dump if tree |
|---|
| 137 | STDERR.print "yaml dumping...\n" if kibitz |
|---|
| 138 | yml = YAML::dump(tree)+"\n" |
|---|
| 139 | STDERR.print "yaml gsub'ing...\n" if kibitz |
|---|
| 140 | p5yml = yml |
|---|
| 141 | p5yml.gsub!(/^(\s*):/,'\1') |
|---|
| 142 | p5yml.gsub!(/^(\s*rule: ):/,'\1') |
|---|
| 143 | p5yml.gsub!(/ !ruby\/object:Match/,' !!perl/hash:Match') |
|---|
| 144 | STDERR.print "done.\n" if kibitz |
|---|
| 145 | print out(p5yml) |
|---|
| 146 | else |
|---|
| 147 | STDERR.print "Unknown format: #{format}\n" |
|---|
| 148 | print_usage_and_exit |
|---|
| 149 | end |
|---|
| 150 | end |
|---|
| 151 | |
|---|
| 152 | def cached_output_for(code,format) |
|---|
| 153 | cachedir = ENV['STD_RED_CACHEDIR'] |
|---|
| 154 | return nil if not cachedir or not format |
|---|
| 155 | input = code+format |
|---|
| 156 | require 'digest/md5' |
|---|
| 157 | std_file = File.dirname($0)+"/std.rb" |
|---|
| 158 | std_code = File.open(std_file,"r"){|f|f.read} |
|---|
| 159 | std_sig = Digest::MD5.hexdigest(std_code) |
|---|
| 160 | input_sig = Digest::MD5.hexdigest(input) |
|---|
| 161 | @cache_file = cachedir+'/parse_'+std_sig.slice(0,8)+'_'+input_sig.slice(0,32) |
|---|
| 162 | if File.exists? @cache_file |
|---|
| 163 | File.open(@cache_file,"r:utf-8"){|f|f.read} |
|---|
| 164 | else |
|---|
| 165 | nil |
|---|
| 166 | end |
|---|
| 167 | end |
|---|
| 168 | def out(output) |
|---|
| 169 | output = output.force_encoding('utf-8') |
|---|
| 170 | File.open(@cache_file,"w"){|f|f.print output} if @cache_file |
|---|
| 171 | output |
|---|
| 172 | end |
|---|
| 173 | |
|---|
| 174 | end |
|---|
| 175 | STDRedRun.new.main |
|---|
| 176 | |
|---|