This repository has been archived on 2022-01-17. You can view files and clone it, but cannot push or open issues/pull-requests.
recipes-parser/src/spec.cr

382 lines
7.5 KiB
Crystal

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