[−][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 aMoltInt
, 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 theDisplay
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 |