dmendel / bindata

BinData - Reading and Writing Binary Data in Ruby
BSD 2-Clause "Simplified" License
577 stars 55 forks source link

Registry namespacing #69

Closed sumofparts closed 8 years ago

sumofparts commented 8 years ago

I have an issue with namespacing that I'd love to get some advice on.

It would appear that the registry implemented in lib/bindata/registry.rb truncates the namespace of anything being registered to simply the rightmost portion of the namespace. For example, if I have:

module Foo
  class Bar < BinData::Record
    uint32le :baz
  end
end

The record is registered as just bar, rather than foo_bar. I realize this might be for reasons of brevity. After all, referencing bar in a subsequent record is much more convenient than referencing foo_bar. However, in projects with a lot of record classes, it forces unduly long class names for the record classes to avoid collisions in the registry.

Would it be acceptable to treat the parent namespace for a record definition as a 'working namespace' such that record definitions would be stored with a fully namespaced path in the registry, but could be referenced by adjacent record definitions with the currently used (rightmost) name?

Further: perhaps anything subclassing BinData::Primitive would be available globally, but anything subclassing BinData::Record would need to share the same parent namespace.

What I have in mind:

module Foo
  class Bar < BinData::Record
    uint32le :baz
    uint32le :bif
  end

  # In namespace Foo, will find bar from registry
  class Fizz < BinData::Record
    bar :buzz
  end
end

module NotFoo
  # Not in namespace Foo, will fail to find bar via registry
  class < BinData::Record
    bar :wont_work
  end
end

Thanks for your consideration. I'd be happy to take a stab at implementing this if it would fall within the goals of the library.

dmendel commented 8 years ago

I agree that having unduly long names (to prevent name collisions) is a problem.

I don't think that namespacing using Ruby modules is a good idea, due to ambiguity between static vs dynamic scoping.

Here's why in code:

We would expect the following to resolve bar.

module A
  class Bar < BinData::Record ; end

  module B
    class Foo <  BinData::Record
      bar: baz
    end
  end
end

Now the ambiguous case. What is the desired way to resolve bar?

prototype = nil
module A
  class Bar << BinData::Record ; end

  prototype = BinData::Struct.new(:fields => [:bar, :baz])
end

module B
  class Bar << BinData::Record ; end

  prototype.new ## should we use A::Bar or B::Bar?
end

This is ambiguous for the reader.

I'll think on a better way to design this, as I agree that this is an issue.

dmendel commented 8 years ago

Let me know if this works for you.

Proposal:

Add an import keyword that behaves similarly to endian.

Background:

Type names in BinData are in a flat namespace. Ruby modules are disregarded. This eliminates the incidence of name conflicts (due to scoping) at the expense of requiring a longer name.

The endian keyword allows the user a shortcut to shorten the name of a type. e.g. endian :little allows a user to specify int16 instead of int16le.

Details:

The proposed import keyword allows the user to shorten the name of a type. While endian shortens the end of the name, import will shorten the beginning.

Example:

class MyNsA < BinData::Record ; end
class MyNsB < BinData::Record ; end

class MyNsMyRecord < BinData::Record
  import :my_ns

  a       :field1   # resolved via the import shortcut
  my_ns_b :field2   # fully qualified name
end

Notes:

This proposal continues to ignore Ruby modules, to prevent problems with mixing static and dynamic scoping. The namespace of a BinData type remains encoded into the class name.

sumofparts commented 8 years ago

I think this would work quite nicely for my needs. Would 'prefix' perhaps be a more semantically helpful name vs 'import'?

dmendel commented 8 years ago

Implemented in 02786d7

The keyword is search_prefix. This allows searching multiple, prioritised namespaces.

class MyRecord < BinData::Record
  search_prefix :ns1, :ns2

  a :field1   # searches for ns1_a then ns2_a
end