.syntax program
outarg = '$' <'@out.print @match # Print last matched token on output stream'>
| .string <'@out.print ' $ ' # Print literal string on output stream'>;
out = '<' *outarg '>' <'@out.print ānā # Print newline on output stream'>;
exp3 = .id <'compile_' $ ' # Call the method for a rule'>
| .string <'scan ' $> | '.id' <'scan /[A-Za-z]+[A-Za-z0-9_]+/'> | '.string' <'scan /\047[^\047]*\047/'> | '(' exp1 ')' | '.e' <'@match = true # .e means empty so always matches => set flag.'> | '*' <'begin'> exp3 <'end while @match # Loop while there is a match'> <'@match = true # Since also zero matches is ok set flag here.'>;
exp2 = ( exp3 <'if @match'> | out <'if true'> )
*( exp3 <'report_error() unless @match'> | out ) <'end'>;
exp1 = <'begin'> exp2
*( '|' <'break if @match'> exp2 ) <'end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise'>;
rule = .id <'def compile_' $>
'=' exp1 ';' <'end'>;
program = '.syntax' .id
<'#!/usr/bin/env ruby'> <'require "strscan"'> <'class StringScanner'> <' # Add scan for string since base StringScanner lack that...'> <' def scan_str(string)'> <' return nil unless self.peek(string.length) == string'> <' self.pos += string.length # Advance the position in the input string'> <' return string'> <' end'> <' alias :old_scan :scan'> <' def scan(strOrRegexp)'> <' String === strOrRegexp ? scan_str(strOrRegexp) : old_scan(strOrRegexp)'> <' end'> <'end'> <'class MetaCompiler_' $> <' $compiler_class = self # Save class in global var for later reference below'> <' def compile_string(string, outFile)'> <' @in, @out = StringScanner.new(string), outFile'> <' compile_' $ ' # call the main compile method to start compiling'> <' end'> <' # Scan for a string or regexp and update state based on match. Skips leading whitespace.'> <' def scan strOrRegexp'> <' @in.scan /\s*/ # Skip whitespace'> <' @match = @in.scan strOrRegexp'> <' # Since nil is same as false in Ruby we can set the flag to the matched token'> <' @last_matched_token = @match if @match # Update last matched only if a token was matched'> <' end'> <' def report_error'> <' pre_lines = @in.string[0, @in.pos].split("\n") # lines of input up to current position'> <' post_lines = @in.rest.split("\n") # lines of input after current position'> <' message = "PARSE ERROR at line #{pre_lines.length}:\n " + pre_lines.last.inspect + " @ "'> <' message += post_lines.first.inspect'> <' message += "\n Last matched token: #{@last_matched_token}"'> <' raise message'> <' end'> <' def self.compile_file(inFile, out = nil)'> <' outfh = (out == nil ? STDOUT : File.open(out, "w"))'> <' self.new.compile_string(File.read(inFile), outfh)'> <' outfh.close if out == nil'> <' end'> *rule '.end' <'end'> <'if ARGV.length < 1 || ARGV.length > 2'> <' puts "ERROR: wrong number of parameters\n\n"'> <' puts "Usage: #{File.basename($0)} <input_file> [output_file]"'> <' exit(-1)'> <'else'> <' $compiler_class.compile_file(ARGV[0], ARGV[1])'> <'end'>;
.end