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