diff options
author | Tom Rondeau | 2012-12-18 15:30:58 -0500 |
---|---|---|
committer | Tom Rondeau | 2012-12-18 15:30:58 -0500 |
commit | 77ea309277382d198681476976bd353a2a98e908 (patch) | |
tree | 203354a4d1b543588d4cc3a1c1409c64f27295a0 /docs/doxygen/other/pmt.dox | |
parent | a21c5f703a2030b4109811ccf3bcb2906df5d466 (diff) | |
download | gnuradio-77ea309277382d198681476976bd353a2a98e908.tar.gz gnuradio-77ea309277382d198681476976bd353a2a98e908.tar.bz2 gnuradio-77ea309277382d198681476976bd353a2a98e908.zip |
docs: added a doxygen manual page describing PMT types and how to use them.
Diffstat (limited to 'docs/doxygen/other/pmt.dox')
-rw-r--r-- | docs/doxygen/other/pmt.dox | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/docs/doxygen/other/pmt.dox b/docs/doxygen/other/pmt.dox new file mode 100644 index 000000000..61b73bca1 --- /dev/null +++ b/docs/doxygen/other/pmt.dox @@ -0,0 +1,348 @@ +/*! \page page_pmt Polymorphic Types + +\section intro Introduction + +Polymorphic Types are opaque data types that are designed as generic +containers of data that can be safely passed around between blocks and +threads in GNU Radio. They are heavily used in the stream tags and +message passing interfaces. The most complete list of PMT function is, +of course, the source code, specifically the header file pmt.h. This +manual page summarizes the most important features and points of PMTs. + + +\section datatype PMT Data Type + +All PMTs are of the type pmt::pmt_t. This is an opaque container and +PMT functions must be used to manipulate and even do things like +compare PMTs. PMTs are also \a immutable (except PMT vectors). We +never change the data in a PMT; instead, we create a new PMT with the +new data. The main reason for this is thread safety. We can pass PMTs +as tags and messages between blocks and each receives its own copy +that we can read from. However, we can never write to this object, and +so if multiple blocks have a reference to the same PMT, there is no +possibility of thread-safety issues of one reading the PMT data while +another is writing the data. If a block is trying to write new data to +a PMT, it actually creates a new PMT to put the data into. Thus we +allow easy access to data in the PMT format without worrying about +mutex locking and unlocking while manipulating them. + +PMTs can represent the following: + +- Boolean values of true/false +- Strings (as symbols) +- Integers (long and uint64) +- Floats (as doubles) +- Complex (as two doubles) +- Pairs +- Tuples +- Vectors (of PMTs) +- Uniform vectors (of any standard data type) +- Dictionaries (list of key:value pairs) +- Any (contains a boost::any pointer to hold anything) + +The PMT library also defines a set of functions that operate directly +on PMTs such as: + +- Equal/equivalence between PMTs +- Length (of a tuple or vector) +- Map (apply a function to all elements in the PMT) +- Reverse +- Get a PMT at a position in a list +- Serialize and deserialize +- Printing + +The constants in the PMT library are: + +- pmt::PMT_T - a PMT True +- pmt::PMT_F - a PMT False +- pmt::PMT_NIL - an empty PMT (think Python's 'None') + +\section insert Inserting and Extracting Data + +Use pmt.h for a complete guide to the list of functions used to create +PMTs and get the data from a PMT. When using these functions, remember +that while PMTs are opaque and designed to hold any data, the data +underneath is still a C++ typed object, and so the right type of +set/get function must be used for the data type. + +Typically, a PMT object can be made from a scalar item using a call +like "pmt::pmt_from_<type>". Similarly, when getting data out of a +PMT, we use a call like "pmt::pmt_to_<type>". For example: + +\code +double a = 1.2345; +pmt::pmt_t pmt_a = pmt::pmt_from_double(a); +double b = pmt::pmt_to_double(pmt_a); + +int c = 12345; +pmt::pmt_t pmt_c = pmt::pmt_from_long(c); +int d = pmt::pmt_to_long(pmt_c); +\endcode + +As a side-note, making a PMT from a complex number is not obvious: + +\code +std::complex<double> a(1.2, 3.4); +pmt::pmt_t pmt_a = pmt::pmt_make_rectangular(a.real(), b.imag()); +std::complex<double> b = pmt::pmt_to_complex(pmt_a); +\endcode + +Pairs, dictionaries, and vectors have different constructors and ways +to manipulate them, and these are explained in their own sections. + + +\section strings Strings + +PMTs have a way of representing short strings. These strings are +actually stored as interned symbols in a hash table, so in other +words, only one PMT object for a given string exists. If creating a +new symbol from a string, if that string already exists in the hash +table, the constructor will return a reference to the existing PMT. + +We create strings with the following functions, where the second +function, pmt::pmt_intern, is simply an alias of the first. + +\code +pmt::pmt_t str0 = pmt::pmt_string_to_symbol(std::string("some string")); +pmt::pmt_t str1 = pmt::pmt_intern(std::string("some string")); +\endcode + +The string can be retrieved using the inverse function: + +\code +std::string s = pmt::pmt_symbol_to_string(str0); +\endcode + + +\section tests Tests and Comparisons + +The PMT library comes with a number of functions to test and compare +PMT objects. In general, for any PMT data type, there is an equivalent +"pmt::pmt_is_<type>". We can use these to test the PMT before trying +to access the data inside. Expanding our examples above, we have: + +\code +pmt::pmt_t str0 = pmt::pmt_string_to_symbol(std::string("some string")); +if(pmt::pmt_is_symbol(str0)) + std::string s = pmt::pmt_symbol_to_string(str0); + +double a = 1.2345; +pmt::pmt_t pmt_a = pmt::pmt_from_double(a); +if(pmt::pmt_is_double(pmt_a)) + double b = pmt::pmt_to_double(pmt_a); + +int c = 12345; +pmt::pmt_t pmt_c = pmt::pmt_from_long(c); +if(pmt::pmt_is_long(pmt_a)) + int d = pmt::pmt_to_long(pmt_c); + +\\ This will fail the test. Otherwise, trying to coerce \b pmt_c as a +\\ double when internally it is a long will result in an exception. +if(pmt::pmt_is_double(pmt_a)) + double d = pmt::pmt_to_double(pmt_c); + +\endcode + + +\section dict Dictionaries + +PMT dictionaries and lists of key:value pairs. They have a +well-defined interface for creating, adding, removing, and accessing +items in the dictionary. Note that every operation that changes the +dictionary both takes a PMT dictionary as an argument and returns a +PMT dictionary. The dictionary used as an input is not changed and the +returned dictionary is a new PMT with the changes made there. + +The following is a list of PMT dictionary functions. Click through to +get more information on what each does. + +- bool pmt::pmt_is_dict(const pmt_t &obj) +- pmt_t pmt::pmt_make_dict() +- pmt_t pmt::pmt_dict_add(const pmt_t &dict, const pmt_t &key, const pmt_t &value) +- pmt_t pmt::pmt_dict_delete(const pmt_t &dict, const pmt_t &key) +- bool pmt::pmt_dict_has_key(const pmt_t &dict, const pmt_t &key) +- pmt_t pmt::pmt_dict_ref(const pmt_t &dict, const pmt_t &key, const pmt_t ¬_found) +- pmt_t pmt::pmt_dict_items(pmt_t dict) +- pmt_t pmt::pmt_dict_keys(pmt_t dict) +- pmt_t pmt::pmt_dict_values(pmt_t dict) + +This example does some basic manipulations of PMT dictionaries in +Python. Notice that we pass the dictionary \a a and return the results +to \a a. This still creates a new dictionary and removes the local +reference to the old dictionary. This just keeps our number of +variables small. + +\code +from gruel import pmt + +key0 = pmt.pmt_intern("int") +val0 = pmt.pmt_from_long(123) +val1 = pmt.pmt_from_long(234) + +key1 = pmt.pmt_intern("double") +val2 = pmt.pmt_from_double(5.4321) + +# Make an empty dictionary +a = pmt.pmt_make_dict() + +# Add a key:value pair to the dictionary +a = pmt.pmt_dict_add(a, key0, val0) +pmt.pmt_print(a) + +# Add a new value to the same key; +# new dict will still have one item with new value +a = pmt.pmt_dict_add(a, key0, val1) +pmt.pmt_print(a) + +# Add a new key:value pair +a = pmt.pmt_dict_add(a, key1, val2) +pmt.pmt_print(a) + +# Test if we have a key, then delete it +print pmt.pmt_dict_has_key(a, key1) +a = pmt.pmt_dict_delete(a, key1) +print pmt.pmt_dict_has_key(a, key1) + +ref = pmt.pmt_dict_ref(a, key0, pmt.PMT_NIL) +pmt.pmt_print(ref) + +# The following should never print +if(pmt.pmt_dict_has_key(a, key0) and pmt.pmt_eq(ref, pmt.PMT_NIL)): + print "Trouble! We have key0, but it returned PMT_NIL" +\endcode + +\section vectors Vectors + +PMT vectors come in two forms: vectors of PMTs and vectors of uniform +data. The standard PMT vector is a vector of PMTs, and each PMT can be +of any internal type. On the other hand, uniform PMTs are of a +specific data type which come in the form: + +- (u)int8 +- (u)int16 +- (u)int32 +- (u)int64 +- float32 +- float64 +- complex 32 (std::complex<float>) +- complex 64 (std::complex<double>) + +That is, the standard sizes of integers, floats, and complex types of +both signed and unsigned. + +Vectors have a well-defined interface that allows us to make, set, +get, and fill them. We can also get the length of a vector with +pmt::pmt_length. + +For standard vectors, these functions look like: + +- bool pmt::pmt_is_vector(pmt_t x) +- pmt_t pmt::pmt_make_vector(size_t k, pmt_t fill) +- pmt_t pmt::pmt_vector_ref(pmt_t vector, size_t k) +- void pmt::pmt_vector_set(pmt_t vector, size_t k, pmt_t obj) +- void pmt::pmt_vector_fill(pmt_t vector, pmt_t fill) + +Uniform vectors have the same types of functions, but they are data +type-dependent. The following list tries to explain them where you +substitute the specific data type prefix for \a dtype (prefixes being: +u8, u16, u32, u64, s8, s16, s32, s64, f32, f64, c32, c64). + +- bool pmt::pmt_is_(dtype)vector(pmt_t x) +- pmt_t pmt::pmt_make_(dtype)vector(size_t k, (dtype) fill) +- pmt_t pmt::pmt_init_(dtype)vector(size_t k, const (dtype*) data) +- pmt_t pmt::pmt_init_(dtype)vector(size_t k, const std::vector<dtype> data) +- pmt_t pmt::pmt_(dtype)vector_ref(pmt_t vector, size_t k) +- void pmt::pmt_(dtype)vector_set(pmt_t vector, size_t k, (dtype) x) +- const dtype* pmt::pmt_(dtype)vector_elements(pmt_t vector, size_t &len) +- dtype* pmt::pmt_(dtype)vector_writable_elements(pmt_t vector, size_t &len) + +\b Note: We break the contract with vectors. The 'set' functions +actually change the data underneath. It is important to keep track of +the implications of setting a new value as well as accessing the +'vector_writable_elements' data. Since these are mostly standard data +types, sets and gets are atomic, so it is unlikely to cause a great +deal of harm. But it's only unlikely, not impossible. Best to use +mutexes whenever manipulating data in a vector. + + +\subsection blob BLOB + +A BLOB is a 'binary large object' type. In PMT's, this is actually +just a thin wrapper around a u8vector. + +\section pairs Pairs + +Pairs are inspired by LISP 'cons' data types, so you will find the +language here comes from LISP. A pair is just a pair of PMT +objects. They are manipulated using the following functions: + +- bool pmt::pmt_is_pair (const pmt_t &obj): Return true if obj is a pair, else false +- pmt_t pmt::pmt_cons(const pmt_t &x, const pmt_t &y): construct new pair +- pmt_t pmt::pmt_car(const pmt_t &pair): get the car of the pair (first object) +- pmt_t pmt::pmt_cdr(const pmt_t &pair): get the cdr of the pair (second object) +- void pmt::pmt_set_car(pmt_t pair, pmt_t value): Stores value in the car field +- void pmt::pmt_set_cdr(pmt_t pair, pmt_t value): Stores value in the cdr field + + +\section serdes Serializing and Deserializing + +It is often important to hide the fact that we are working with PMTs +to make them easier to transmit, store, write to file, etc. The PMT +library has methods to serialize data into a string buffer or a +string and then methods to deserialize the string buffer or string +back into a PMT. We use this extensively in the metadata files (see +\ref page_metadata). + +- bool pmt::pmt_serialize(pmt_t obj, std::streambuf &sink) +- std::string pmt::pmt_serialize_str(pmt_t obj) +- pmt_t pmt::pmt_deserialize(std::streambuf &source) +- pmt_t pmt::pmt_deserialize_str(std::string str) + +For example, we will serialize the data above to make it into a string +ready to be written to a file and then deserialize it back to its +original PMT. + +\code +from gruel import pmt + +key0 = pmt.pmt_intern("int") +val0 = pmt.pmt_from_long(123) + +key1 = pmt.pmt_intern("double") +val1 = pmt.pmt_from_double(5.4321) + +# Make an empty dictionary +a = pmt.pmt_make_dict() + +# Add a key:value pair to the dictionary +a = pmt.pmt_dict_add(a, key0, val0) +a = pmt.pmt_dict_add(a, key1, val1) + +pmt.pmt_print(a) + +ser_str = pmt.pmt_serialize_str(a) +print ser_str + +b = pmt.pmt_deserialize_str(ser_str) +pmt.pmt_print(b) + +\endcode + +The line where we 'print ser_str' will print and parts will be +readable, but the point of serializing is not to make a human-readable +string. This is only done here as a test. + + +\section printing Printing + +We have used the pmt::pmt_print function in these examples to nicely +print the contents of a PMT. Another way to print the contents is +using the overloaded "<<" operator with a stream buffer object. In +C++, we can inline print the contents of a PMT like: + +\code +pmt::pmt_t a pmt::pmt_from_double(1.0); +std::cout << "The PMT a contains " << a << std::endl; +\endcode + +*/ |