[][src]Module molt::value

The Value Type

The Value struct is the standard representation of a data value in the Molt language. It represents a single immutable data value; the data is reference-counted, so instances can be cloned efficiently. Its content may be any TCL data value: a number, a list, a string, or a value of an arbitrary type that meets certain requirements.

In TCL, "everything is a string". Every value is defined by its string representation, e.g., "one two three" is the string rep of a list with three items, "one", "two", and "three". A string that is a valid string for multiple types can be interpreted as any of those types; for example, the string "5" can be a string, the integer 5, or a list of one element, "5".

Value is not Sync!

A Value is associated with a particular Interp and changes internally to optimize performance within that Interp. Consequently, Values are not Sync. Values may be used to pass values between Interps in the same thread (at the cost of potential shimmering), but between threads one should pass the string rep instead.

Comparisons

If two Value's are compared for equality in Rust, Rust compares their string reps. In TCL expressions the == and != operators compare numbers and the eq and ne operators compare string reps.

Internal Representation

"Everything is a string"; thus, every Value has a string representation, or string rep. But for efficiency with numbers, lists, and user-defined binary data structures, the Value also caches a data representation, or data rep.

A Value can have just a string rep, just a data rep, or both. Like the Tcl_Obj in standard TCL, the Value is like a stork: it can stand one leg, the other leg, or both legs.

A client can ask the Value for its string, which is always available and will be computed from the data rep if it doesn't already exist. (Once computed, the string rep never changes.) A client can also ask the Value for any other type it desires. If the requested data rep is already available, it will be returned; otherwise, the Value will attempt to parse it from the string_rep. The last successful conversion is cached for later.

For example, consider the following sequence:

  • A computation yields a Value containing the integer 5. The data rep is a MoltInt, and the string rep is undefined.

  • The client asks for the string, and the string rep "5" is computed.

  • The client asks for the value's integer value. It's available and is returned.

  • The client asks for the value's value as a MoltList. This is possible, because the string "5" can be interpreted as a list of one element, the string "5". A new data rep is computed and saved, replacing the previous one.

With this scheme, long series of computations can be carried out efficiently using only the the data rep, incurring the parsing cost at most once, while preserving TCL's "everything is a string" semantics.

Converting from one data rep to another is expensive, as it involves parsing the string value. Performance suffers when code switches rapidly from one data rep to another, e.g., in a tight loop. The effect, which is known as "shimmering", can usually be avoided with a little care.

Value handles strings, integers, floating-point values, and lists as special cases, since they are part of the language and are so frequently used. In addition, a Value can also contain external types: Rust types that meet certain requirements.

External Types

Any type that implements the std::fmt::Display, std::fmt::Debug, and std::str::FromStr traits can be saved in a Value. The struct's Display and FromStr trait implementations are used to convert between the string rep and data rep.

  • The Display implementation is responsible for producing the value's string rep.

  • The FromStr implementation is responsible for producing the value's data rep from a string, and so must be able to parse the Display implementation's output.

  • The string rep should be chosen so as to fit in well with TCL syntax, lest confusion, quoting hell, and comedy should ensue. (You'll know it when you see it.)

Example

For example, the following code shows how to define an external type implementing a simple enum.

use molt::types::*;
use std::fmt;
use std::str::FromStr;

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Flavor {
    SALTY,
    SWEET,
}

impl fmt::Display for Flavor {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if *self == Flavor::SALTY {
            write!(f, "salty")
        } else {
            write!(f, "sweet")
        }
    }
}

impl FromStr for Flavor {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let value = value.to_lowercase();

        if value == "salty" {
            Ok(Flavor::SALTY)
        } else if value == "sweet" {
            Ok(Flavor::SWEET)
        } else {
           // The error message doesn't matter to Molt
           Err("Not a flavor string".to_string())
        }
    }
}

impl Flavor {
    /// A convenience: retrieves the enumerated value, converting it from
    /// `Option<Flavor>` into `Result<Flavor,ResultCode>`.
    pub fn from_molt(value: &Value) -> Result<Self, ResultCode> {
        if let Some(x) = value.as_copy::<Flavor>() {
            Ok(x)
        } else {
            Err(ResultCode::Error(Value::from("Not a flavor string")))
        }
    }
}

Structs

Value

The Value type. See the module level documentation for more.