# Martin Fowler's Reader Framework example from his "Language Workbenches"
# and "Generating Code for DSLs" papers.  
# H. Conrad Cunningham 
# Original:  9 October 2006
# Revision: 10 October 2006

# READING AN EXTERNAL TEXT DSL USING A SINGLE-PASS PARSER

require 'ReaderFramework'
require 'ReaderUtilities'

# Class ReaderBuilderTextSinglePass encapsulates a simple single pass parser
# for the text DSL.  It takes the "filename" for the DSL description file
# and configures the Reader framework.

# Example DSL text description input:
#   mapping SVCL dsl.ServiceCall
#     4-18: CustomerName
#     19-23: CustomerID
#     24-27 : CallTypeCode
#     28-35 : DateOfCallString
# 
#   mapping  USGE dsl.Usage
#     4-8 : CustomerID
#     9-22: CustomerName
#     30-30: Cycle
#     31-36: ReadDate

# Method ReaderBuilderTextSinglePass#configure takes the Reader object to be
# configured and configures it based on the declarations in the text DSL
# description file.

class ReaderBuilderTextSinglePass

  include ReaderUtilities

  def initialize(filename)
    @filename = filename
  end

  def configure(reader)
    @reader = reader
    input = File.new(@filename)
    input.each do |line|
      process_line(line)
    end
    input.close
  end

private

  def process_line(line)
    trimmed = line.rstrip
    return if is_blank? trimmed
    return if is_comment? trimmed
    if is_new_mapping? trimmed
      make_new_strategy(trimmed)
    else 
      make_field_extract(trimmed)
    end
  end

  def is_new_mapping?(line)
    line =~ /\s*mapping/
  end

  def make_new_strategy(line)
    tokens = line.scan(/\S+/)
    @current_strategy = ReaderFramework::ReaderStrategy.new(tokens[1],
                                          class_name_only(tokens[2]))
    @reader.add_strategy(@current_strategy)
  end

  def make_field_extract(line)
    tokens = line.split(':')
    target_property = tokens[1].strip
    range_tokens = tokens[0].strip.split('-')
    begin_col = range_tokens[0].to_i
    end_col   = range_tokens[1].to_i
    @current_strategy.add_field_extractor(begin_col, end_col, target_property)
  end

end#ReaderBuilderTextSinglePass


# Classes used to hold the data read

class ServiceCall
end#ServiceCall

class Usage
end#Usage


# TESTING THE SINGLE PASS BUILDER AND FRAMEWORK

class TestTextSinglePass

  def TestTextSinglePass.run
    rdr = ReaderFramework::Reader.new
    builder = ReaderBuilderTextSinglePass.new("dslinput.txt")
    builder.configure(rdr)
    inp = File.new("fowlerdata.txt")
    res = rdr.process(inp)
    inp.close
    res.each {|o| puts o.inspect}
  end

end#TestTextSinglePass
