# encoding: utf-8 # frozen_string_literal: true require "rainbow" module Carbon module Compiler class Metanostic # The "mode" of a metanostic or diagnostic. This determines how a # diagnostic should be treated. module Mode # No mode. This is rarely used. # # @return [Integer] NONE = 0x0 # Ignore mode. This means that by default, the diagnostic should be # ignored by the compiler; with a default or less verbose level, the # diagnostic is not output to the compiler. # # @return [Integer] IGNORE = 0b1 # Informational mode. This means that the diagnostic is output for the # purpose of informing a developer of a certain thing; for example, # the original definition of a function. At the default verbose level, # it is output to the compiler. # # @return [Integer] INFO = 0b10 # Warning mode. This means that the diagnostic is output to inform the # developer of a potential issue. Enough diagnostic warnings causes # a panic. At the default verbose level, it is output to the compiler. # # @return [Integer] WARNING = 0b100 # Error mode. This means that the diagnostic is output to inform the # developer of a definite issue. The compiler will not be able to # finish, and enough diagnostic errors causes a panic. At the default # verbose level, it is output to the compiler. # # @return [Integer] ERROR = 0b1000 # Panic mode. This means that the diagnostic is output to inform the # developer of a fatal issue. Once the panic is emitted, all execution # stops, and all diagnostics output. # # @return [Integer] PANIC = 0b10000 # All of the above. This is mostly used for metanostics to note that # all of the above are allowable modes for a given diagnostic. # # @return [Integer] ALL = PANIC | ERROR | WARNING | INFO | IGNORE # An associative list of modes to convert from strings. # # @return [{String => Integer}] MODES = { "none" => NONE, "ignore" => IGNORE, "info" => INFO, "warning" => WARNING, "error" => ERROR, "panic" => PANIC, "all" => ALL }.freeze # Converts from a given value into a mode. If the value is an array, # the return value of the function is the result of converting each # element into a mode and unioning the result. If the value is an array, # {MODES} is used to convert the string. If the value is numeric, # it is returned. # # @raise [ArgumentError] If value is not an Array, a String, or a # Numeric. # @param value [Array, String, Numeric] # @return [Integer] def self.from(value) if value.is_a?(::Array) from_array(value) elsif value.is_a?(::String) from_string(value) elsif value.is_a?(::Numeric) value else fail ArgumentError, "Unexpected value #{value.class}" end end # Converts the given Array value into a mode. It does this by converting # each element into a mode, and unioning the modes. # # @param value [] # @return [Integer] def self.from_array(value) value.inject(NONE) { |a, e| a | from(e) } end # Converts the given String value into a mode. It does this by fetching # the mode from the {MODES} constant. # # @raise KeyError If value is not a valid mode. # @param value [String] # @return [Integer] def self.from_string(value) MODES.fetch(value.downcase) end # Checks if the given type is a mode. This is done by performing a # bitwise and, and comparing the result against zero. The order of the # arguments does not matter. # # @param mode [Integer] The mode to check against. This might be the # allowed modes of a {Metanostic}. # @param type [Integer] The mode to check for. This might be the new # default value of a {Metanostic}. # @return [Boolean] def self.is?(mode, type) (mode & type) != 0 end # Determines if a given mode is a singular mode (i.e. only one of # {IGNORE}. {INFO}, {WARNING}, {ERROR}, or {PANIC}). It does this # via doing a population count. # # @param mode [Integer] # @return [Boolean] def self.singular?(mode) count = 0 ((count += mode & 1) && mode >>= 1) until mode == 0 count < 2 end # Converts the given mode into a string. If the value is {ALL}, it # retuns `"All"`; if the value is {NONE}, it returns `"None"`; # otherwise, it attempts to find all of the modes that are represented # by the value. # # @return [String] def self.to_s(value) return "All" if value == ALL return "None" if value == NONE MODES.select { |_, v| is?(value, v) && v != ALL } .map(&:first) .map(&:capitalize) .join(", ") end # Creates a rainbow representation of the given mode. The mode must # be singular (see {.singular?}). # # @see https://github.com/sickill/rainbow # @param mode [Integer] # @return [Rainbow] def self.to_rainbow(mode) case mode when IGNORE then Rainbow("Ignore").color(:white).bright when INFO then Rainbow("Info").color(:blue).bright when WARNING then Rainbow("Warning").color(:yellow).bright when ERROR then Rainbow("Error").color(:red).bright when PANIC then Rainbow("Panic").color(:magenta).bright end end end end end end