# 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