require "./parser" class Specs class StringContainer property value : String def initialize(@value) end end class LongStringContainer property value : String def initialize(@value) end end class SectionContainer property value : String def initialize(@value) end end class ArrayContainer property value : Array(String) def initialize(@value) end end property assignments : Hash(String, StringContainer | LongStringContainer | SectionContainer | ArrayContainer) def initialize @assignments = Hash(String, StringContainer | LongStringContainer | SectionContainer | ArrayContainer).new end def parse_hashglob(tree) # hashglob = hash hashglob | any hashglob | any | hash str = String.build do |str| case tree when Pegasus::Generated::TerminalTree str << tree.string when Pegasus::Generated::NonterminalTree tree.children.each do |it| str << parse_hashglob it end end end str end def parse_freetext(tree) # tabs hashglob cr freetext? case tree when Pegasus::Generated::TerminalTree raise "freetext: should not be terminal" when Pegasus::Generated::NonterminalTree current_line = parse_hashglob tree.children[1] current_line = current_line.lstrip(" ").rstrip(" ") str = String.build do |str| str << current_line str << "\n" if tree.children.size == 4 str << parse_freetext tree.children[3] end end str end end def parse_list_items(tree) # tabs dash glob cr listitem? | tabs comment cr listitem case tree when Pegasus::Generated::TerminalTree raise "parse_list_items: should not be terminal" when Pegasus::Generated::NonterminalTree if tree.children[1] == Pegasus::Generated::NonterminalTree str = String.build do |str| str << parse_list_items tree.children[3] end str else current_line = parse_glob tree.children[2] current_line = current_line.lstrip(" ").rstrip(" ") str = String.build do |str| str << current_line str << "\n" if tree.children.size == 5 str << parse_list_items tree.children[4] end end str end end end def parse_list(tree) # string colon cr listitem name = parse_string tree.children[0] value = parse_list_items tree.children[3] if name.nil? return end if value.nil? return end @assignments[name] = ArrayContainer.new value.split("\n") end def parse_freetextblock(tree) # at multiplealphanum cr freetext name = parse_multiplealphanum tree.children[1] value = parse_freetext tree.children[3] if name.nil? return end if value.nil? return end @assignments[name] = LongStringContainer.new value end def parse_glob (tree) # glob = any glob | any str = String.build do |str| case tree when Pegasus::Generated::TerminalTree str << tree.string when Pegasus::Generated::NonterminalTree tree.children.each do |it| str << parse_glob it end end end str end def parse_multiplealphanum(tree) # multiplealphanum = alphanum multiplealphanum? # alphanum = string | numbers str = String.build do |str| case tree when Pegasus::Generated::TerminalTree str << tree.string when Pegasus::Generated::NonterminalTree tree.children.each do |it| str << parse_multiplealphanum it end end end str end def parse_string(tree) case tree when Pegasus::Generated::TerminalTree return tree.string else raise "this should be a string but this is non terminal" end end def parse_assignment(tree) # string colon glob name = parse_string tree.children[0] value = parse_glob tree.children[2] value = value.lstrip(" ").rstrip(" ") if name.nil? return end if value.nil? return end @assignments[name] = StringContainer.new value end def parse_tree(tree) case tree when Pegasus::Generated::TerminalTree # do nothing when Pegasus::Generated::NonterminalTree case tree.name when "freetextblock" ; parse_freetextblock tree when "freetext" ; parse_freetext tree when "assignment" ; parse_assignment tree when "list" ; parse_list tree when "comment" # do nothing else tree.children.each { |it| parse_tree(it) } end end end def replace_string_obj (v : StringContainer | SectionContainer | LongStringContainer) reg = /%\{([^}]*)\}/ is_missing_references = false value = v.value begin while value =~ reg x = reg.match(value) unless x.nil? var = x.captures() if var[0]? && @assignments[var[0]]? replacement_object = @assignments[var[0]] case replacement_object when StringContainer replacement_value = replacement_object.value value = value.gsub "%{#{var[0]}}", "#{replacement_value}" end else is_missing_references = true raise "cannot find variable \033[31m#{var[0]}\033[00m" end end v.value = value value = v.value end rescue e puts "#{e}" end if is_missing_references raise "there are missing references in the document: fix it" end end def replace_array_obj (v : ArrayContainer) reg = /%\{([^}]*)\}/ str_array = v.value newarray = Array(String).new is_missing_references = false str_array.each do |value| tmp = value begin while tmp =~ reg x = reg.match(tmp) unless x.nil? var = x.captures() if var[0]? && @assignments[var[0]]? replacement_object = @assignments[var[0]] case replacement_object when StringContainer replacement_value = replacement_object.value tmp = tmp.gsub "%{#{var[0]}}", "#{replacement_value}" end else is_missing_references = true raise "cannot find variable \033[31m#{var[0]}\033[00m" end end end rescue e puts "#{e}" end newarray.push tmp end if is_missing_references raise "there are missing references in the document: fix it" end v.value = newarray end def rewrite # replaces all occurences of %{variable} by the content of @assignments[variable] @assignments.map do |k, v| case v when StringContainer replace_string_obj v when SectionContainer replace_string_obj v when LongStringContainer replace_string_obj v when ArrayContainer replace_array_obj v end end end def self.has_file_bugs? (content : String) lines = content.split "\n" previous_line_started_with_tab = false line_count = 1 has_bugs = false # 1. detect lacking "\n" after a block of text lines.each do |line| if previous_line_started_with_tab unless /^$|^\t/.match(line) puts "there is a problem line #{line_count}: lacking a carriage return" return true end end line_count += 1 previous_line_started_with_tab = /^\t/.match(line) if line_count - 1 == lines.size if previous_line_started_with_tab puts "there is a problem with end of file: lacking a carriage return" has_bugs = true end end end has_bugs end def self.parse(file_name : String, options : Hash(String, String) | Nil = nil) : Specs | Nil begin content = File.read(file_name) content = content.rchop # XXX: detect simple and known grammar bugs if Specs.has_file_bugs? (content) raise "the file #{file_name} has bugs" end specs = Specs.new unless options.nil? options.each do |opt, val| specs.assignments[opt] = StringContainer.new val end end tree = Pegasus::Generated.process(content) specs.parse_tree tree specs.rewrite specs rescue e puts "Exception: #{e}" nil end end end