summaryrefslogtreecommitdiff
path: root/lib/prism/pack.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/prism/pack.rb')
-rw-r--r--lib/prism/pack.rb228
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