# frozen_string_literal: true # The MIT License (MIT) # # Copyright (c) 2019 NUM Technology Ltd # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'modl/parser/MODLParserBaseListener' require 'modl/parser/global_parse_context' require 'modl/parser/ref_processor' require 'modl/parser/unicode_escapes' require 'modl/parser/substitutions' require 'modl/parser/file_importer' require 'antlr4/runtime/parse_cancellation_exception' require 'modl/parser/sutil' require 'modl/parser/modl_class' require 'modl/parser/modl_method' require 'modl/parser/modl_index' require 'modl/parser/modl_keylist' require 'modl/parser/evaluator' require 'cgi' require 'net/http' module MODL module Parser # This class represents a MODL parse tree for a given MODL object. # It tries to process the parse tree as it is generated as much as # possible to save revisiting nodes unnecessarily. # # Many of the method names are generated by ANTLR4 so are not ruby style. class Parsed < MODL::Parser::MODLParserBaseListener attr_accessor :structures attr_accessor :global def initialize(global = nil) @global = global @structures = [] end def enterModl(ctx) @global = GlobalParseContext.new if @global.nil? ctx_modl_structure = ctx.modl_structure ctx_modl_structure.each do |str| structure = ParsedStructure.new @global str.enter_rule(structure) @structures << structure end @structures = @global.structures + @structures @global end def self.additional_string_processing(text) # Special case for a possibly empty graved string `` unless text.nil? match_data = /^`([^`]*)`$/.match text return match_data[1] if match_data&.length&.positive? end text end # Class to represent a parsed grammar object class ParsedMap < MODL::Parser::MODLParserBaseListener attr_accessor :mapItems def initialize(global) @global = global @mapItems = [] end def find_property(key) if key.is_a? Integer return @mapItems[key] else @mapItems.each do |mi| return mi.pair if mi.pair.key == key end end end def enterModl_map(ctx) modl_map_item = ctx.modl_map_item return if modl_map_item.nil? modl_map_item.each do |mi| map_item = ParsedMapItem.new @global mi.enter_rule(map_item) @mapItems << map_item end end def extract_hash result = {} @mapItems.each do |i| i_hash = i.extract_hash next unless i_hash.is_a? Hash i_hash.keys.each do |k| result[k] = i_hash[k] end end result.is_a?(Array) && result.length == 1 ? result[0] : result end end # Class to represent a parsed grammar object class ParsedMapItem < MODL::Parser::MODLParserBaseListener attr_accessor :pair attr_accessor :mapConditional def initialize(global) @global = global end def enterModl_map_item(ctx) modl_pair = ctx.modl_pair unless modl_pair.nil? @pair = ParsedPair.new @global modl_pair.enter_rule(@pair) end modl_map_conditional = ctx.modl_map_conditional return if modl_map_conditional.nil? @mapConditional = ParsedMapConditional.new @global modl_map_conditional.enter_rule(@mapConditional) end def extract_hash return @pair.extract_hash if @pair return @mapConditional.extract_hash if @mapConditional end end # Class to represent a parsed grammar object class ParsedStructure < MODL::Parser::MODLParserBaseListener attr_accessor :array attr_accessor :pair attr_accessor :top_level_conditional attr_accessor :map def initialize(global) @global = global end def enterModl_structure(ctx) modl_pair = ctx.modl_pair modl_top_level_conditional = ctx.modl_top_level_conditional modl_map = ctx.modl_map modl_array = ctx.modl_array if !modl_pair.nil? @pair = ParsedPair.new @global modl_pair.enter_rule(@pair) elsif !modl_top_level_conditional.nil? @top_level_conditional = ParsedTopLevelConditional.new @global modl_top_level_conditional.enter_rule(@top_level_conditional) elsif !modl_map.nil? @map = ParsedMap.new @global modl_map.enter_rule(@map) elsif !modl_array.nil? @array = ParsedArray.new @global modl_array.enter_rule(@array) end end def extract_hash return @array.extract_hash if @array return @pair.extract_hash if @pair return @top_level_conditional.extract_hash if @top_level_conditional return @map.extract_hash if @map end end # Class to represent a parsed grammar object class ParsedPair < MODL::Parser::MODLParserBaseListener attr_accessor :key attr_accessor :map attr_accessor :array attr_accessor :valueItem attr_accessor :key_lists attr_accessor :type # A string set to the type of pair that we have found bases on its key attr_accessor :text # The simple text value rather than the object attr_accessor :final attr_accessor :loaded # For *load instructions so we don't load twice def initialize(global) @global = global @needs_defref = true @final = false @file_importer = FileImporter.instance @loaded = false @array = nil @map = nil end def find_property(key) return self if key == @key return @map.find_property(key) if @map return @array.find_property(key) if @array return @valueItem.find_property(key) if @valueItem end # Set the appropriate field base on the value type def set_value(value) if value.is_a? Array @map = nil @array = ParsedArray.new @global @array.abstractArrayItems = [] value.each do |item| array_item = ParsedArrayItem.new @global array_item.arrayValueItem = ParsedArrayValueItem.new @global array_item.arrayValueItem.primitive = ParsedPrimitive.new(@global) array_item.arrayValueItem.primitive.string = ParsedString.new(item) array_item.arrayValueItem.primitive.text = item @array.abstractArrayItems << array_item end @valueItem = nil @text = @array.extract_hash return elsif value.is_a?(ParsedPair) @map = value.map ? value.map : nil @array = value.array ? value.array : nil @valueItem = value.valueItem ? value.valueItem : nil return elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) @map = nil @array = nil @valueItem = ParsedValueItem.new @global @valueItem.value = ParsedValue.new @global @valueItem.value.primitive = ParsedPrimitive.new(@global) if value @valueItem.value.primitive.trueVal = ParsedTrue.instance else @valueItem.value.primitive.falseVal = ParsedFalse.instance end @valueItem.value.primitive.text = value @valueItem.value.text = value @text = value return end value = value.extract_hash unless value.is_a?(String) || value.is_a?(Integer) @map = nil @array = nil @valueItem = ParsedValueItem.new @global @valueItem.value = ParsedString.new(value) @text = value end # Convert this object to a simple hash ready for JSON.generate def extract_hash value = @array.extract_hash if @array value = @valueItem.extract_hash if @valueItem value = @map.extract_hash if @map @text = value return if @type == 'index' return if @type == 'hidden' return if @type == 'version' return if @type == 'class' return if @type == 'method' return if @type == 'import' return if @type == 'allow' return if @type == 'expect' formatted_key = Substitutions.process UnicodeEscapes.process @key {formatted_key => @text} end def enterModl_pair(ctx) @type = 'pair' # default the type to an ordinary pair ctx_string = ctx.STRING @key = ctx_string.to_s unless ctx_string.nil? ctx_quoted = ctx.QUOTED unless ctx_quoted.nil? @key = ctx_quoted.to_s @key = Sutil.toptail(@key) # remove the quotes end raise InterpreterError, 'Invalid key - null, true, or false keys are not allowed.' if @key.nil? if @key.include?('%') || @key.include?('`') @key, new_value = RefProcessor.deref @key, @global unless @key.is_a?(String) raise InterpreterError, "Error: '" + @key.to_s + "' is an invalid key." if new_value.nil? @key = new_value.is_a?(String) ? new_value : new_value.text end raise InterpreterError, "Error: '" + @key.to_s + "' should de-ref to a string." unless key.is_a?(String) end @final = true if @key.upcase == @key modl_array = ctx.modl_array modl_map = ctx.modl_map modl_value_item = ctx.modl_value_item if !modl_array.nil? @array = ParsedArray.new @global modl_array.enter_rule(@array) elsif !modl_map.nil? @map = ParsedMap.new @global modl_map.enter_rule(@map) elsif !modl_value_item.nil? @valueItem = ParsedValueItem.new @global modl_value_item.enter_rule(@valueItem) end set_pair_type raise InterpreterError, 'Invalid keyword: ' + @key if @type == 'pair' && @key.start_with?('*') validate_key if @type == 'pair' || @type == 'hidden' # Type-specific processing case @type when 'class' ClassExtractor.extract(self, @global) when 'id' extract_value when 'name' extract_value when 'superclass' extract_value when 'allow' extract_value when 'keylist' KeylistExtractor.extract(self, @valueItem) if @valueItem KeylistExtractor.extract(self, @array) if @array when 'version' extract_value raise InterpreterError, 'Invalid MODL version: ' + @valueItem.value.primitive.text if @valueItem.value.primitive.number.nil? raise InterpreterError, 'Invalid MODL version: ' + @valueItem.value.primitive.number.num.to_s if @valueItem.value.primitive.number.num.is_a? Float raise InterpreterError, 'Invalid MODL version: ' + @valueItem.value.primitive.number.num.to_s if @valueItem.value.primitive.number.num.zero? raise InterpreterError, 'MODL version should be on the first line if specified.' if @global.has_pairs? @global.syntax_version = @valueItem.value.primitive.number.num when 'method' MethodExtractor.extract(self, @global) when 'transform' extract_transform @valueItem when 'import' files = @valueItem.extract_hash if @valueItem files = @array.extract_hash if @array @file_importer.import_files files, @global unless @global.in_condition? when 'index' IndexExtractor.extract(self, @global) when 'hidden' extract_value else extract_value end return if @global.in_condition? # Don't store pairs in conditionals until we evaluate the conditions if @key.start_with? '_' k = Sutil.tail(@key) existing = @global.pair(k) raise InterpreterError, 'Already defined ' + k + ' as final.' if existing&.final && @type != "import" raise InterpreterError, 'Cannot load multiple files after *LOAD instruction' if existing&.final && @type == "import" @global.pair(k, self) end existing = @global.pair(@key) raise InterpreterError, 'Already defined ' + @key + ' as final.' if existing&.final && @type != "import" raise InterpreterError, 'Cannot load multiple files after *LOAD instruction' if existing&.final && @type == "import" @global.pair(@key, self) end private def extract_value item = @valueItem @text = item.value.text if item.is_a?(ParsedValueItem) && item.value @text = item.valueItem.value.text if item.is_a?(ParsedPair) invoke_deref end def extract_transform item @transform = item.value.primitive.string.string end def validate_key invalid_chars = "£!$@-+'*#^&" invalid_chars.each_char do |c| next unless @key.include?(c) raise InterpreterError, 'Invalid key - "' + c + '" character not allowed: ' + @key end key = @key.start_with?('_') ? Sutil.tail(@key) : @key raise InterpreterError, 'Invalid key - "' + key + '" - entirely numeric keys are not allowed: ' + @key if key == key.to_i.to_s end def invoke_deref return unless @needs_defref && !@text.nil? && @text.is_a?(String) && @text.include?('%') @needs_defref = false @text, new_value = RefProcessor.deref @text, @global if new_value.is_a? ParsedMap @map = new_value @valueItem = nil @array = nil elsif new_value.is_a? ParsedArray @array = new_value @valueItem = nil @map = nil elsif new_value.is_a? ParsedValueItem @valueItem = new_value elsif new_value.nil? set_value @text elsif new_value.is_a? ParsedPair set_value @text if @text set_value new_value if @text.nil? elsif new_value.is_a? String set_value @text else set_value(new_value) end end # Set the pair type if its a 'special' type def set_pair_type @type = 'class' if @key == '*c' || @key == '*class' if @key == '*C' || @key == '*CLASS' @type = 'class' @key = @key.downcase end @type = 'id' if @key == '*i' || @key == '*id' @type = 'name' if @key == '*n' || @key == '*name' @type = 'name' if @key == '*N' || @key == '*NAME' @type = 'superclass' if @key == '*S' || @key == '*SUPERCLASS' @type = 'superclass' if @key == '*s' || @key == '*superclass' @type = 'keylist' if @key == '*a' || @key == '*assign' @type = 'version' if @key == '*V' || @key == '*VERSION' @type = 'method' if @key == '*m' || @key == '*method' @type = 'transform' if @key == '*t' || @key == '*transform' if @key == '*L' || @key == '*LOAD' @key = @key.downcase @type = 'import' if @array @global.freeze_max_files(@array.abstractArrayItems.length) elsif @valueItem&.value&.array @global.freeze_max_files(@valueItem&.value&.array.abstractArrayItems.length) elsif @valueItem&.value&.nbArray @global.freeze_max_files(@valueItem&.value&.nbArray.arrayItems.length) else @global.freeze_max_files(1) end end if @key == '*l' || @key == '*load' @type = 'import' end @type = 'index' if @key == '?' @type = 'hidden' if @key.start_with? '_' @type = 'allow' if @key.downcase == '*allow' end end # Class to represent a parsed grammar object class ParsedArrayValueItem < MODL::Parser::MODLParserBaseListener attr_accessor :map attr_accessor :array attr_accessor :pair attr_accessor :primitive attr_accessor :text # The simple text value rather than the object def initialize(global) @global = global end def find_property(key) return @map.find_property(key) if @map return @array.find_property(key) if @array return @nbArray.find_property(key) if @nbArray return @pair.find_property(key) if @pair return @primitive.find_property(key) if @primitive end def extract_hash return @map.extract_hash if @map return @array.extract_hash if @array return @nbArray.extract_hash if @nbArray return @pair.extract_hash if @pair return @primitive.extract_hash if @primitive @text end def enterModl_array_value_item(ctx) @text = nil modl_map = ctx.modl_map modl_array = ctx.modl_array modl_pair = ctx.modl_pair modl_primitive = ctx.modl_primitive if !modl_map.nil? @map = ParsedMap.new @global modl_map.enter_rule(@map) elsif !modl_array.nil? @array = ParsedArray.new @global modl_array.enter_rule(@array) elsif !modl_pair.nil? @pair = ParsedPair.new @global modl_pair.enter_rule(@pair) elsif !modl_primitive.nil? @primitive = ParsedPrimitive.new @global modl_primitive.enter_rule(@primitive) @text = @primitive.text end # ignoring comments! end end # Class to represent a parsed grammar object class ParsedValueItem < MODL::Parser::MODLParserBaseListener attr_accessor :value attr_accessor :valueConditional def initialize(global) @global = global end def find_property(key) @value.find_property(key) if @value end def enterModl_value_item(ctx) modl_value_conditional = ctx.modl_value_conditional unless modl_value_conditional.nil? @valueConditional = ParsedValueConditional.new @global modl_value_conditional.enter_rule(@valueConditional) end modl_value = ctx.modl_value return if modl_value.nil? @value = ParsedValue.new @global modl_value.enter_rule(@value) end def extract_hash return @value.extract_hash if @value return @valueConditional.extract_hash if @valueConditional end end # Class to represent a parsed grammar object class ParsedValue < MODL::Parser::MODLParserBaseListener attr_accessor :map attr_accessor :array attr_accessor :nbArray attr_accessor :pair attr_accessor :primitive attr_accessor :text # The simple text value rather than the object def initialize(global) @global = global end def find_property(key) return @map.find_property(key) if @map return @array.find_property(key) if @array return @nbArray.find_property(key) if @nbArray return @pair.find_property(key) if @pair return @primitive.find_property(key) if @primitive end def extract_hash return @map.extract_hash if @map return @array.extract_hash if @array return @nbArray.extract_hash if @nbArray return @pair.extract_hash if @pair return @primitive.extract_hash if @primitive @text end def evaluate return @primitive.evaluate if @primitive true end def value_obj return @map if @map return @array if @array return @nbArray if @nbArray return @pair if @pair return @primitive if @primitive end def enterModl_value(ctx) modl_map = ctx.modl_map modl_nb_array = ctx.modl_nb_array modl_array = ctx.modl_array modl_pair = ctx.modl_pair modl_primitive = ctx.modl_primitive if !modl_map.nil? @map = ParsedMap.new @global modl_map.enter_rule(@map) elsif !modl_nb_array.nil? @nbArray = ParsedNbArray.new @global modl_nb_array.enter_rule(@nbArray) elsif !modl_array.nil? @array = ParsedArray.new @global modl_array.enter_rule(@array) elsif !modl_pair.nil? @pair = ParsedPair.new @global modl_pair.enter_rule(@pair) elsif !modl_primitive.nil? @primitive = ParsedPrimitive.new @global modl_primitive.enter_rule(@primitive) @text = @primitive.text end # ignoring comments! end end # Class to represent a parsed grammar object class ParsedPrimitive < MODL::Parser::MODLParserBaseListener attr_accessor :quoted attr_accessor :number attr_accessor :trueVal attr_accessor :falseVal attr_accessor :nilVal attr_accessor :string attr_accessor :constant attr_accessor :text # The simple text value rather than the object def initialize(global) @global = global @constant = false end def find_property(key) if @string user_method = @global.user_method(key) if user_method return user_method.run(@string.string) end StandardMethods.run_method(key, Substitutions.process(UnicodeEscapes.process(@string.string))) end end def extract_hash result, _ignore = RefProcessor.deref(@text, @global) unless @constant result = @text if @constant Substitutions.process UnicodeEscapes.process result end def evaluate return false if @nilVal return false if @falseVal true end def value_obj return @quoted if @quoted return @number if @number return @trueVal if @trueVal return @falseVal if @falseVal return @nilVal if @nilVal return @string if @string @text end def enterModl_primitive(ctx) ctx_number = ctx.NUMBER ctx_string = ctx.STRING ctx_quoted = ctx.QUOTED ctx_null = ctx.NULL ctx_true = ctx.TRUE ctx_false = ctx.FALSE if !ctx_number.nil? @number = ParsedNumber.new(ctx_number.text) @text = @number.num elsif !ctx_string.nil? @text = ctx_string.text @constant = @text.start_with?('`') && !@text.include?('%') && !@text.include?('`.') @string = ParsedString.new(@text) @text = @string.string elsif !ctx_quoted.nil? @constant = true @text = Sutil.toptail ctx_quoted.text @quoted = ParsedQuoted.new(@text) elsif !ctx_null.nil? @nilVal = ParsedNull.instance @text = nil elsif !ctx_true.nil? @trueVal = ParsedTrue.instance @text = true elsif !ctx_false.nil? @falseVal = ParsedFalse.instance @text = false end # ignoring comments! end end # Class to represent a parsed grammar object class ParsedString attr_accessor :string def initialize(string) @string = string end def text @string end def extract_hash @string = Substitutions.process UnicodeEscapes.process @string end end # Class to represent a parsed grammar object class ParsedNumber attr_accessor :num def initialize(string) @num = string.include?('.') ? string.to_f : string.to_i end end # Class to represent a parsed grammar object class ParsedQuoted attr_accessor :string def initialize(string) @string = string end end # Class to represent a parsed grammar object class ParsedConditionTest < MODL::Parser::MODLParserBaseListener attr_accessor :subConditionList def initialize(global) @global = global @subConditionList = [] end def evaluate result = false @subConditionList.each do |s| last_operator = s.b.a should_negate = s.b.b partial = s.a.evaluate case last_operator when '&' result &= should_negate ? !partial : partial when '|' result |= should_negate ? !partial : partial else result |= should_negate ? !partial : partial end end result end def enterModl_condition_test(ctx) ctx_children = ctx.children unless ctx_children.empty? last_operator = nil should_negate = false ctx_children.each do |child| if child.is_a? MODLParser::Modl_condition_groupContext condition_group = ParsedConditionGroup.new @global child.enter_rule(condition_group) p2 = OpenStruct.new p2.a = last_operator p2.b = should_negate p1 = OpenStruct.new p1.a = condition_group p1.b = p2 @subConditionList << p1 last_operator = nil should_negate = false elsif child.is_a? MODLParser::Modl_conditionContext condition = ParsedCondition.new @global child.enter_rule(condition) p2 = OpenStruct.new p2.a = last_operator p2.b = should_negate p1 = OpenStruct.new p1.a = condition p1.b = p2 @subConditionList << p1 last_operator = nil should_negate = false else if child.text == '!' should_negate = true else last_operator = child.text end end end end end end # Class to represent a parsed grammar object class ParsedConditionGroup < MODL::Parser::MODLParserBaseListener attr_accessor :conditionsTestList def initialize(global) @global = global @conditionsTestList = [] end def evaluate result = false @conditionsTestList.each do |s| partial = s.a.evaluate result |= partial end result end def enterModl_condition_group(ctx) ctx_children = ctx.children return if ctx_children.empty? last_operator = nil ctx_children.each do |child| if child.is_a? MODLParser::Modl_condition_testContext condition_test = ParsedConditionTest.new @global child.enter_rule(condition_test) p = OpenStruct.new p.a = condition_test p.b = last_operator @conditionsTestList << p last_operator = nil else last_operator = child.text if (child.text != '') && (child.text != '}') end end end end # Class to represent a parsed grammar object class ParsedCondition < MODL::Parser::MODLParserBaseListener attr_accessor :values attr_accessor :operator attr_accessor :text def initialize(global) @global = global @values = [] end def evaluate Evaluator.evaluate(@global, self) end def enterModl_condition(ctx) modl_operator = ctx.modl_operator @operator = modl_operator.text unless modl_operator.nil? modl_value = ctx.modl_value modl_value.each do |v| value = ParsedValue.new @global v.enter_rule(value) @values << value end ctx_string = ctx.STRING if !ctx_string.nil? @text = Parsed.additional_string_processing(ctx_string.text) @string = ParsedString.new(@text) @text = @string.string end end end # Class to represent a parsed grammar object class ParsedMapConditionalReturn < MODL::Parser::MODLParserBaseListener attr_accessor :mapItems def initialize(global) @global = global @mapItems = [] end def extract_hash @mapItems[0].extract_hash end def enterModl_map_conditional_return(ctx) modl_map_item = ctx.modl_map_item return if modl_map_item.empty? modl_map_item.each do |mi| map_item = ParsedMapItem.new @global mi.enter_rule(map_item) @mapItems << map_item end end end # Class to represent a parsed grammar object class ParsedMapConditional < MODL::Parser::MODLParserBaseListener attr_accessor :conditionTests attr_accessor :mapConditionalReturns def initialize(global) @global = global @conditionTests = [] @mapConditionalReturns = [] end def extract_hash return @mapConditionalReturns[0].extract_hash if @result @mapConditionalReturns[1].extract_hash if @mapConditionalReturns.length > 1 end def enterModl_map_conditional(ctx) i = 0 modl_condition_test = ctx.modl_condition_test ctx_modl_map_conditional_return = ctx.modl_map_conditional_return while i < modl_condition_test.size condition_test = ParsedConditionTest.new @global ctx.modl_condition_test_i(i).enter_rule(condition_test) conditional_return = ParsedMapConditionalReturn.new @global ctx.modl_map_conditional_return_i(i).enter_rule(conditional_return) @conditionTests[i] = condition_test @mapConditionalReturns[i] = conditional_return if ctx_modl_map_conditional_return.size > modl_condition_test.size i += 1 conditional_return = ParsedMapConditionalReturn.new @global ctx.modl_map_conditional_return_i(ctx_modl_map_conditional_return.size - 1).enter_rule(conditional_return) @mapConditionalReturns[i] = conditional_return end i += 1 end @result = @conditionTests[0].evaluate end end # Class to represent a parsed grammar object class ParsedTopLevelConditionalReturn < MODL::Parser::MODLParserBaseListener attr_accessor :structures def initialize(global) @global = global @structures = [] end def extract_hash return @structures[0].extract_hash if @structures.length == 1 result = [] @structures.each do |s| hash = s.extract_hash result << hash unless hash.nil? end return result unless result.length == 1 return result[0] if result.length == 1 end def enterModl_top_level_conditional_return(ctx) modl_structure = ctx.modl_structure return if modl_structure.empty? modl_structure.each do |str| structure = ParsedStructure.new @global str.enter_rule(structure) @structures << structure end end end # Class to represent a parsed grammar object class ParsedTopLevelConditional < MODL::Parser::MODLParserBaseListener attr_accessor :conditionTests attr_accessor :topLevelConditionalReturns def initialize(global) @global = global @topLevelConditionalReturns = [] @conditionTests = [] @file_importer = FileImporter.instance end def extract_hash @conditionTests.each_index do |i| next unless @conditionTests[i].evaluate item = @topLevelConditionalReturns[i] if item.structures[0].pair key = item.structures[0].pair.key key = Sutil.tail(key) if key[0] == '_' pair = item.structures[0].pair @global.pair(key, pair) if key.downcase.start_with? '*l' files = pair.valueItem.extract_hash if pair.valueItem files = pair.array.extract_hash if pair.array unless pair.loaded @file_importer.import_files files, @global pair.loaded = true end end end item.structures = OrphanHandler.adopt(@global, item.structures) return item.extract_hash end return unless @topLevelConditionalReturns.length > @conditionTests.length last_item = @topLevelConditionalReturns[-1] if last_item.structures[0].pair key = last_item.structures[0].pair.key key = Sutil.tail(key) if key[0] == '_' pair = last_item.structures[0].pair @global.pair(key, pair) if key.downcase.start_with? '*l' files = pair.valueItem.extract_hash if pair.valueItem files = pair.array.extract_hash if pair.array unless pair.loaded @file_importer.import_files files, @global pair.loaded = true end end end last_item.extract_hash end def enterModl_top_level_conditional(ctx) @global.enter_condition i = 0 modl_condition_test = ctx.modl_condition_test ctx_modl_top_level_conditional_return = ctx.modl_top_level_conditional_return while i < modl_condition_test.size condition_test = ParsedConditionTest.new @global ctx.modl_condition_test_i(i).enter_rule(condition_test) conditional_return = ParsedTopLevelConditionalReturn.new @global ctx.modl_top_level_conditional_return_i(i).enter_rule(conditional_return) @conditionTests[i] = condition_test @topLevelConditionalReturns[i] = conditional_return i += 1 end if ctx_modl_top_level_conditional_return.size > modl_condition_test.size conditional_return = ParsedTopLevelConditionalReturn.new @global ctx.modl_top_level_conditional_return_i(ctx_modl_top_level_conditional_return.size - 1).enter_rule(conditional_return) @topLevelConditionalReturns[i] = conditional_return end extract_hash @global.exit_condition end end # Class to represent a parsed grammar object class ParsedArrayConditionalReturn < MODL::Parser::MODLParserBaseListener attr_accessor :arrayItems def initialize(global) @global = global @arrayItems = [] end def extract_hash @arrayItems[0].arrayValueItem.text end def enterModl_array_conditional_return(ctx) modl_array_item = ctx.modl_array_item return if modl_array_item.empty? modl_array_item.each do |ai| array_item = ParsedArrayItem.new @global ai.enter_rule(array_item) @arrayItems << array_item end end end # Class to represent a parsed grammar object class ParsedArrayConditional < MODL::Parser::MODLParserBaseListener attr_accessor :conditionTest attr_accessor :arrayConditionalReturns def initialize(global) @global = global @conditionTests = [] @arrayConditionalReturns = [] end def extract_hash result = @conditionTests[0].evaluate return @arrayConditionalReturns[0].extract_hash if result @arrayConditionalReturns[1].extract_hash end def enterModl_array_conditional(ctx) i = 0 ctx_modl_condition_test = ctx.modl_condition_test ctx_modl_array_conditional_return = ctx.modl_array_conditional_return while i < ctx_modl_condition_test.size condition_test = ParsedConditionTest.new @global ctx.modl_condition_test_i(i).enter_rule(condition_test) conditional_return = ParsedArrayConditionalReturn.new @global ctx.modl_array_conditional_return_i(i).enter_rule(conditional_return) @conditionTests[i] = condition_test @arrayConditionalReturns[i] = conditional_return if ctx_modl_array_conditional_return.size > ctx_modl_condition_test.size i += 1 condition_test = ParsedConditionTest.new @global conditional_return = ParsedArrayConditionalReturn.new @global ctx.modl_array_conditional_return_i(ctx_modl_array_conditional_return.size - 1).enter_rule(conditional_return) @conditionTests[i] = condition_test @arrayConditionalReturns[i] = conditional_return end i += 1 end end end # Class to represent a parsed grammar object class ParsedValueConditionalReturn < MODL::Parser::MODLParserBaseListener attr_accessor :valueItems def initialize(global) @global = global @valueItems = [] end def extract_hash return @valueItems[0].value.text if @valueItems[0].value.text return @valueItems[0].value.extract_hash end def enterModl_value_conditional_return(ctx) modl_value_item = ctx.modl_value_item return if modl_value_item.empty? modl_value_item.each do |vi| valueItem = ParsedValueItem.new @global vi.enter_rule(valueItem) @valueItems << valueItem end end end # Class to represent a parsed grammar object class ParsedValueConditional < MODL::Parser::MODLParserBaseListener attr_accessor :conditionTests attr_accessor :valueConditionalReturns def initialize(global) @global = global @conditionTests = [] @valueConditionalReturns = [] end def extract_hash return @result if @valueConditionalReturns.length == 0 return @valueConditionalReturns[0].extract_hash if @result return @valueConditionalReturns[1].extract_hash end def enterModl_value_conditional(ctx) i = 0 ctx_modl_condition_test = ctx.modl_condition_test ctx_modl_value_conditional_return = ctx.modl_value_conditional_return while i < ctx_modl_condition_test.size condition_test = ParsedConditionTest.new @global ctx.modl_condition_test_i(i).enter_rule(condition_test) @conditionTests[i] = condition_test break if ctx.modl_value_conditional_return_i(i).nil? conditional_return = ParsedValueConditionalReturn.new @global ctx.modl_value_conditional_return_i(i).enter_rule(conditional_return) @valueConditionalReturns[i] = conditional_return if ctx_modl_value_conditional_return.size > ctx_modl_condition_test.size condition_test = ParsedConditionTest.new @global conditional_return = ParsedValueConditionalReturn.new @global ctx.modl_value_conditional_return_i(ctx_modl_value_conditional_return.size - 1).enter_rule(conditional_return) @conditionTests[i + 1] = condition_test @valueConditionalReturns[i + 1] = conditional_return end i += 1 end @result = @conditionTests[0].evaluate end end # Class to represent a parsed grammar object class ParsedNbArray < MODL::Parser::MODLParserBaseListener attr_accessor :arrayItems def initialize(global) @global = global @arrayItems = [] end def find_property(key) if key.is_a? Integer return @arrayItems[key].arrayValueItem else @arrayItems.each do |mi| return mi.arrayValueItem.pair if mi.arrayValueItem.pair && mi.arrayValueItem.pair.key == key end nil end end def extract_hash result = [] @arrayItems.each do |i| result << i.extract_hash end result end def enterModl_nb_array(ctx) i = 0 previous = nil ctx_children = ctx.children ctx_children.each do |pt| if pt.is_a? MODLParser::Modl_array_itemContext array_item = ParsedArrayItem.new @global pt.enter_rule(array_item) @arrayItems[i] = array_item i += 1 elsif pt.is_a? Antlr4::Runtime::TerminalNode if !previous.nil? && previous.is_a?(Antlr4::Runtime::TerminalNode) && pt.is_a?(Antlr4::Runtime::TerminalNode) # If we get here then we have two terminal nodes in a row, so we need to output something unless # the terminal symbols are newlines # prev_symbol = previous.symbol.type current_symbol = pt.symbol.type if prev_symbol == MODLLexer::COLON && current_symbol == MODLLexer::COLON array_item = Parsed.handle_empty_array_item @arrayItems[i] = array_item i += 1 end end end previous = pt end end end def self.handle_empty_array_item # Create something for the blank array item # # The problem is that we might not have any context to tell us what type we need to create # so this currently defaults to the nil value # # TODO : Is there a way to know the type to create or is nil always acceptable? array_item = ParsedArrayItem.new @global array_item.arrayValueItem = ParsedArrayValueItem.new @global array_item.arrayValueItem.primitive = ParsedPrimitive.new @global array_item.arrayValueItem.primitive.nilVal = ParsedNull.instance array_item end # Class to represent a parsed grammar object class ParsedArray < MODL::Parser::MODLParserBaseListener # We now have a list of < array_item | nb_array > attr_accessor :abstractArrayItems def initialize(global) @global = global @abstractArrayItems = [] end def find_property(key) if key.is_a? Integer return @abstractArrayItems[key] else @abstractArrayItems.each do |mi| return mi.arrayValueItem.pair if mi.arrayValueItem.pair && mi.arrayValueItem.pair.key == key end nil end end def extract_hash result = [] abstractArrayItems.each do |i| result << i.extract_hash end result end def enterModl_array(ctx) # Create the new abstractArrayItems list first, sized to the total of array_item.size and nb_array.size i = 0 previous = nil ctx_children = ctx.children ctx_children.each do |pt| if pt.is_a? MODLParser::Modl_array_itemContext array_item = ParsedArrayItem.new @global pt.enter_rule(array_item) @abstractArrayItems[i] = array_item i += 1 elsif pt.is_a? MODLParser::Modl_nb_arrayContext nb_array = ParsedNbArray.new @global pt.enter_rule(nb_array) @abstractArrayItems[i] = nb_array i += 1 elsif pt.is_a? Antlr4::Runtime::TerminalNode if !previous.nil? && previous.is_a?(Antlr4::Runtime::TerminalNode) && pt.is_a?(Antlr4::Runtime::TerminalNode) # If we get here then we have two terminal nodes in a row, so we need to output something unless # the terminal symbols are newlines # prev_symbol = previous.symbol.type current_symbol = pt.symbol.type if prev_symbol == MODLLexer::LSBRAC && current_symbol == MODLLexer::RSBRAC next # This allows empty arrays end if prev_symbol == MODLLexer::STRUCT_SEP && current_symbol == MODLLexer::STRUCT_SEP # Create something for the blank array item # # The problem is that we might not have any context to tell us what type we need to create # so this currently defaults to the nil # # TODO : Is there a way to know the type to create or is nil always acceptable? array_item = Parsed.handle_empty_array_item @abstractArrayItems[i] = array_item i += 1 end end end previous = pt end end end # Class to represent a parsed grammar object class ParsedArrayItem < MODL::Parser::MODLParserBaseListener attr_accessor :arrayValueItem attr_accessor :arrayConditional def initialize(global) @global = global end def find_property(key) return @arrayValueItem.find_property(key) end def enterModl_array_item(ctx) ctx_modl_array_conditional = ctx.modl_array_conditional unless ctx_modl_array_conditional.nil? @arrayConditional = ParsedArrayConditional.new @global ctx_modl_array_conditional.enter_rule(@arrayConditional) end ctx_modl_array_value_item = ctx.modl_array_value_item unless ctx_modl_array_value_item.nil? @arrayValueItem = ParsedArrayValueItem.new @global ctx_modl_array_value_item.enter_rule(@arrayValueItem) end end def extract_hash return @arrayValueItem.extract_hash if @arrayValueItem return @arrayConditional.extract_hash if @arrayConditional end end # Singleton class to represent a true value class ParsedTrue < MODL::Parser::MODLParserBaseListener include Singleton end # Singleton class to represent a false value class ParsedFalse < MODL::Parser::MODLParserBaseListener include Singleton end # Singleton class to represent a null value class ParsedNull < MODL::Parser::MODLParserBaseListener include Singleton end # Convert the parse tree to a simpler structure suitable for JSON.generate. def extract_hash result = [] if @structures.length.positive? @structures.each do |s| value = s.extract_hash result << value unless value.nil? end else result = {} end case result.length when 0 return nil when 1 return result[0] end result end end end end