ruby/lib/prism/pack.rb

229 строки
5.9 KiB
Ruby

# frozen_string_literal: true
# typed: ignore
module Prism
# A parser for the pack template language.
module Pack
%i[
SPACE
COMMENT
INTEGER
UTF8
BER
FLOAT
STRING_SPACE_PADDED
STRING_NULL_PADDED
STRING_NULL_TERMINATED
STRING_MSB
STRING_LSB
STRING_HEX_HIGH
STRING_HEX_LOW
STRING_UU
STRING_MIME
STRING_BASE64
STRING_FIXED
STRING_POINTER
MOVE
BACK
NULL
UNSIGNED
SIGNED
SIGNED_NA
AGNOSTIC_ENDIAN
LITTLE_ENDIAN
BIG_ENDIAN
NATIVE_ENDIAN
ENDIAN_NA
SIZE_SHORT
SIZE_INT
SIZE_LONG
SIZE_LONG_LONG
SIZE_8
SIZE_16
SIZE_32
SIZE_64
SIZE_P
SIZE_NA
LENGTH_FIXED
LENGTH_MAX
LENGTH_RELATIVE
LENGTH_NA
].each do |const|
const_set(const, const)
end
# A directive in the pack template language.
class Directive
# A symbol representing the version of Ruby.
attr_reader :version
# A symbol representing whether or not we are packing or unpacking.
attr_reader :variant
# A byteslice of the source string that this directive represents.
attr_reader :source
# The type of the directive.
attr_reader :type
# The type of signedness of the directive.
attr_reader :signed
# The type of endianness of the directive.
attr_reader :endian
# The size of the directive.
attr_reader :size
# The length type of this directive (used for integers).
attr_reader :length_type
# The length of this directive (used for integers).
attr_reader :length
# Initialize a new directive with the given values.
def initialize(version, variant, source, type, signed, endian, size, length_type, length)
@version = version
@variant = variant
@source = source
@type = type
@signed = signed
@endian = endian
@size = size
@length_type = length_type
@length = length
end
# The descriptions of the various types of endianness.
ENDIAN_DESCRIPTIONS = {
AGNOSTIC_ENDIAN: "agnostic",
LITTLE_ENDIAN: "little-endian (VAX)",
BIG_ENDIAN: "big-endian (network)",
NATIVE_ENDIAN: "native-endian",
ENDIAN_NA: "n/a"
}
# The descriptions of the various types of signedness.
SIGNED_DESCRIPTIONS = {
UNSIGNED: "unsigned",
SIGNED: "signed",
SIGNED_NA: "n/a"
}
# The descriptions of the various types of sizes.
SIZE_DESCRIPTIONS = {
SIZE_SHORT: "short",
SIZE_INT: "int-width",
SIZE_LONG: "long",
SIZE_LONG_LONG: "long long",
SIZE_8: "8-bit",
SIZE_16: "16-bit",
SIZE_32: "32-bit",
SIZE_64: "64-bit",
SIZE_P: "pointer-width"
}
# Provide a human-readable description of the directive.
def describe
case type
when SPACE
"whitespace"
when COMMENT
"comment"
when INTEGER
if size == SIZE_8
base = "#{SIGNED_DESCRIPTIONS[signed]} #{SIZE_DESCRIPTIONS[size]} integer"
else
base = "#{SIGNED_DESCRIPTIONS[signed]} #{SIZE_DESCRIPTIONS[size]} #{ENDIAN_DESCRIPTIONS[endian]} integer"
end
case length_type
when LENGTH_FIXED
if length > 1
base + ", x#{length}"
else
base
end
when LENGTH_MAX
base + ", as many as possible"
else
raise
end
when UTF8
"UTF-8 character"
when BER
"BER-compressed integer"
when FLOAT
"#{SIZE_DESCRIPTIONS[size]} #{ENDIAN_DESCRIPTIONS[endian]} float"
when STRING_SPACE_PADDED
"arbitrary binary string (space padded)"
when STRING_NULL_PADDED
"arbitrary binary string (null padded, count is width)"
when STRING_NULL_TERMINATED
"arbitrary binary string (null padded, count is width), except that null is added with *"
when STRING_MSB
"bit string (MSB first)"
when STRING_LSB
"bit string (LSB first)"
when STRING_HEX_HIGH
"hex string (high nibble first)"
when STRING_HEX_LOW
"hex string (low nibble first)"
when STRING_UU
"UU-encoded string"
when STRING_MIME
"quoted printable, MIME encoding"
when STRING_BASE64
"base64 encoded string"
when STRING_FIXED
"pointer to a structure (fixed-length string)"
when STRING_POINTER
"pointer to a null-terminated string"
when MOVE
"move to absolute position"
when BACK
"back up a byte"
when NULL
"null byte"
else
raise
end
end
end
# The result of parsing a pack template.
class Format
# A list of the directives in the template.
attr_reader :directives
# The encoding of the template.
attr_reader :encoding
# Create a new Format with the given directives and encoding.
def initialize(directives, encoding)
@directives = directives
@encoding = encoding
end
# Provide a human-readable description of the format.
def describe
source_width = directives.map { |d| d.source.inspect.length }.max
directive_lines = directives.map do |directive|
if directive.type == SPACE
source = directive.source.inspect
else
source = directive.source
end
# @type var source_width: Integer
" #{source.ljust(source_width)} #{directive.describe}"
end
(["Directives:"] + directive_lines + ["Encoding:", " #{encoding}"]).join("\n")
end
end
end
end