lib/modl/parser/parser.rb in modl-0.3.26 vs lib/modl/parser/parser.rb in modl-0.3.27

- old
+ new

@@ -1,72 +1,285 @@ # 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/throwing_error_listener' -require 'modl/parser/parsed' - module MODL + # A Parser module module Parser + REPLACEMENTS = { + '\t' => "\t", + '\n' => "\n", + '\b' => "\b", + '\f' => "\f", + '\r' => "\r", + '~t' => "\t", + '~n' => "\n", + '~b' => "\b", + '~f' => "\f", + '~r' => "\r", + '~\\' => '\\', + '\\\\' => '\\', + '~~' => '~', + '\~' => '~', + '~(' => '(', + '\(' => '(', + '~)' => ')', + '\)' => ')', + '~[' => '[', + '\[' => '[', + '~]' => ']', + '\]' => ']', + '~;' => ';', + '\;' => ';', + '~`' => '`', + '\`' => '`', + '~"' => '"', + '"' => '"', + '~=' => '=', + '\=' => '=' + }.freeze - # This class converts the input string into a Modl:Parser::Parsed object for further processing. - class Parser - def self.parse(str, global = nil) - begin - lexer = MODL::Parser::MODLLexer.new(Antlr4::Runtime::CharStreams.from_string(str, 'String')) - lexer.remove_error_listeners - lexer.add_error_listener ThrowingErrorListener.instance + def self.parse_modl(str) + tokens = MODL::Tokeniser.tokenise str + if tokens.nil? || tokens.empty? + MODL::Model::Modl.new nil + elsif root_primitive? tokens[0] + MODL::Model::Modl.new tokens[0].value + else + result = parse_structures(tokens) + return MODL::Model::Modl.new result[0] if result.length == 1 - tokens = Antlr4::Runtime::CommonTokenStream.new(lexer) + MODL::Model::Modl.new ModlArray.new(result) + end + end - parser = MODL::Parser::MODLParser.new(tokens) - parser.remove_error_listeners - parser.add_error_listener ThrowingErrorListener.instance + def self.root_primitive?(token) + %i[string quoted null true false integer float].include?(token) + end - global = GlobalParseContext.new if global.nil? + def self.parse_structures(tokens) + result = [] + until tokens.empty? + result.push parse_modl_value(tokens) + expect_separator = tokens.shift + if !expect_separator.nil? && expect_separator.type == :struct_sep + raise ParserError, "Expected ';' near #{tokens}" + end + end + result + end - parsed = Parsed.new(global) - parser.modl.enter_rule(parsed) - parsed - rescue Antlr4::Runtime::ParseCancellationException => e - check_modl_version(global, e) - raise ParserError, 'Parser Error: ' + e.message - rescue StandardError => e - check_modl_version(global, e) - raise InterpreterError, 'Interpreter Error: ' + e.message - rescue InterpreterError => e - check_modl_version(global, e) - raise InterpreterError, 'Interpreter Error: ' + e.message + def self.parse_modl_value(tokens) + first_token = tokens.shift + + case first_token.type + when :lbracket + tokens.unshift first_token + return parse_modl_array tokens + when :lparen + tokens.unshift first_token + return parse_modl_map tokens + when :string || :quoted + peek = tokens[0] + key = first_token.value + if !peek.nil? && peek.type == :equals + tokens.shift + return MODL::Model::ModlPair.new replace_escapes(unquote(key)), parse_pair_value(tokens) end + + if !peek.nil? && (peek.type == :lbracket || peek.type == :lparen) + return MODL::Model::ModlPair.new replace_escapes(unquote(key)), parse_pair_value(tokens) + end + + if peek.nil? || peek.type == :struct_sep || peek.type == :rparen || peek.type == :rbracket + return MODL::Model::ModlString.new(replace_escapes(first_token.value)) if first_token.type == :string + + return MODL::Model::ModlQuoted.new(replace_escapes(unquote(first_token.value))) + end + when :integer + return MODL::Model::ModlInteger.new first_token.value + when :float + return MODL::Model::ModlFloat.new first_token.value + when :null + return MODL::Model::ModlBoolNull::MODL_NULL + when true + return MODL::Model::ModlBoolNull::MODL_TRUE + when false + return MODL::Model::ModlBoolNull::MODL_FALSE + else + tokens.unshift first_token + maybe_primitive = parse_primitive tokens + return maybe_primitive unless maybe_primitive.nil? end + raise ParserError, "Unexpected token: \'#{first_token}\'" + end - def self.check_modl_version(global, e) - if global.syntax_version > global.interpreter_syntax_version - raise InterpreterError, 'Interpreter Error: ' + e.message + ' - MODL Version ' + - global.interpreter_syntax_version.to_s + - ' interpreter cannot process this MODL Version ' + - global.syntax_version.to_s + ' file.' + def self.parse_pair_value(tokens) + first_token = tokens.shift + case first_token.type + when :lbracket + tokens.unshift first_token + return parse_modl_array tokens + when :lparen + tokens.unshift first_token + return parse_modl_map tokens + when :string || :quoted + peek = tokens[0] + + raise ParserError, "Unexpected token: \'#{first_token}\'" if !peek.nil? && peek.type == :equals + + if !peek.nil? && (peek.type == :lbracket || peek.type == :lparen) + raise ParserError, "Unexpected token: \'#{first_token}\'" end + + if peek.nil? || peek.type == :struct_sep || peek.type == :rparen || peek.type == :rbracket + return MODL::Model::ModlString.new replace_escapes(first_token.value) if first_token.type == :string + + return MODL::Model::ModlQuoted.new replace_escapes(unquote(first_token.value)) + end + raise ParserError, "Unexpected token: \'#{first_token}\'" + when :integer + return MODL::Model::ModlInteger.new first_token.value + when :float + return MODL::Model::ModlFloat.new first_token.value + when :null + return MODL::Model::ModlBoolNull::MODL_NULL + when true + return MODL::Model::ModlBoolNull::MODL_TRUE + when false + return MODL::Model::ModlBoolNull::MODL_FALSE + else + tokens.unshift first_token + maybe_primitive = parse_primitive tokens + return maybe_primitive unless maybe_primitive.nil? end + raise ParserError, "Unexpected token: \'#{first_token}\'" + end + + def self.parse_primitive(tokens) + result = nil + tok = tokens.shift + + case tok.type + when :lparen || :rparen || :lbracket || :rbracket || :equals + raise ParserError, "Unexpected token: \'#{tok}\'" if tokens.empty? + + tokens.unshift tok + return nil + when :null + result = MODL::Model::ModlBoolNull::MODL_NULL + when true + result = MODL::Model::ModlBoolNull::MODL_TRUE + when false + result = MODL::Model::ModlBoolNull::MODL_FALSE + when :quoted + result = MODL::Model::ModlQuoted.new replace_escapes(unquote(tok.value)) + when :string + result = MODL::Model::ModlString.new replace_escapes(tok.value) + when :integer + result = MODL::Model::ModlInteger.new tok.value + when :float + result = MODL::Model::ModlFloat.new tok.value + else + raise ParserError, "Unknown token type in: \'#{tok}\'" + end + + peek = tokens[0] + raise ParserError, 'Only one primitive allowed at the root.' if !peek.nil? && peek.type == :struct_sep + + if !peek.nil? && (peek.type == :lparen || peek.type == :lbracket || peek.type == :equals) + s.unshift tok + return nil + elsif !peek.nil? + raise ParserError, "Unexpected token: \'#{peek}\'" + end + result + end + + def self.parse_modl_map(tokens) + first_token = tokens.shift + entries = [] + + until tokens.empty? + peek = tokens[0] + if !peek.nil? && peek.type == :rparen + tokens.shift + break + end + + mp = parse_modl_value tokens + entries.push mp + + peek = tokens[0] + + raise ParserError, "Expected ')' near #{first_token}" if peek.nil? + + case peek.type + when :rparen + tokens.shift + break + when :struct_sep + tokens.shift + peek = tokens[0] + raise ParserError, "Unexpected ; before ] at #{peek}" if !peek.nil? && peek.type == :rparen + end + end + MODL::Model::ModlMap.new entries + end + + def self.parse_modl_array(tokens) + first_token = tokens.shift + entries = [] + + until tokens.empty? + peek = tokens[0] + if !peek.nil? && peek.type == :rbracket + tokens.shift + break + end + + mp = parse_modl_value tokens + entries.push mp + + peek = tokens[0] + + raise ParserError, "Expected ']' near #{first_token}" if peek.nil? + + case peek.type + when :rbracket + tokens.shift + break + when :struct_sep + tokens.shift + peek = tokens[0] + raise ParserError, "Unexpected ; before ] at #{peek}" if !peek.nil? && peek.type == :rparen + end + end + MODL::Model::ModlArray.new entries + end + + def self.unquote(str) + return str unless str.instance_of?(String) + + if (str.start_with?('`') && str.end_with?('`')) || (str.start_with?('"') && str.end_with?('"')) + str[1..-2] + else + str + end + end + + def self.replace_escapes(str) + return str unless str.instance_of?(String) + + result = str + i = 0 + while i < str.length + REPLACEMENTS.each_pair do |key, value| + if result.slice(i..).start_with?(key) + result = result.sub(key, value) + break + end + end + i += 1 + end + result end end end