Optimizations for format and aggregate filling

This is complicated. Let me lay out the two optimizations here, and then some motivating discussion afterwards.

**First, we enhance constant folding of format().** Because the general case is a PITA, we had been tackling it on a case-by-case basis, and in fact previously only folded the "%s" case. In the first patch, we extend this to constant fold "%d", "%s%d", "%s%s", "%d%d", and "%s%d%d" cases.

**Second, we add enhanced simplification of compassign, mxcompassign, and aassign.**
Consider this idiom:

        color C;
        C[0] = 3.14;
        C[1] = 0;
        C[2] = 42.0;

We track constant-ness and known-values by whole symbol, not by individual component of aggregates (like colors or points or arrays). So previously, this code would result in element-by-element assignment of the color, which even then would not be recognized as a constant.

(N.B. That looks like silly code compared to "C = color(3.14,0,42)", but in fact people do this kind of nonsense, and even if they didn't, they might be assigning *non-constant* things that, only part way through optimization, are all turned into constants.)

With this patch, we recognize consecutive component assignment to the same aggregate, and if ALL components get assigned constants, we can replace the whole code sequence with just a single assignment from a constant aggregate, which in all likelihood will lead to the later elision of the variable altogether.

This applies to compassign (component assignment to triples), mxcompassign (component assignment to matrices), and aassign (individual element assignment to arrays).

**What's really up**

OK, here's a shader code fragment that assembles a texture filename on the fly based on a variable:

    int whichmap = ...some computation...;
    string atlasname = format ("%s%d.tx", filename_base, whichmap);
    color C = texture (atlasname, u, v);

This is not uncommon for texture atlasing, but it's very inefficient because we are constructing strings inside every execution of the shader. (And as an aside, the whole "texture filenames not known ahead of time" is very problematic if we ever wish to have a GPU-based back end for OSL.)

However, with the two optimizations of this pull request in place, if you could determine that there were, say, a maximum of 10 textures to be indexed, you could construct this:

    #define NAME(i) format("%s%d.tx",filename,i)
    #define ALLNAMES NAME(0), NAME(1), NAME(2), NAME(3), NAME(4), NAME(5), NAME(6), NAME(7), NAME(8), NAME(9)
    string filenames[10] = { ALLNAMES };
    #undef ALLNAMES
    #undef NAME

    int whichmap = ...some computation...;
    string atlasname = filenames[whichmap];
    color C = texture (atlasname, u, v);

And -- Shazam! -- each individual format() call in the array will be constant-folded into a string literal, and then furthermore it will recognize that the entire array is filled thusly and turn it into a CONSTANT array of filenames that is fully baked in for the whole shader instance and not filled in every time the shader executes. Thus, the filename selection is simply an array lookup of constant strings (underneath it all: just a single pointer copy), and NO STRING CONSTRUCTION happens during shader execution.

This stomps the snot out of certain performance bottlenecks with this kind of texture selection inside OSL shaders.