diff options
Diffstat (limited to 'lib/prism/pack.rb')
-rw-r--r-- | lib/prism/pack.rb | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/lib/prism/pack.rb b/lib/prism/pack.rb new file mode 100644 index 0000000000..c0de8ab8b7 --- /dev/null +++ b/lib/prism/pack.rb @@ -0,0 +1,228 @@ +# 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 |