/*
Copyright (C) 2007-2010, Parrot Foundation.
$Id: codestring.pmc 46390 2010-05-07 15:02:22Z bacek $

=head1 NAME

src/pmc/codestring.pmc - CodeString PMC Class

=head1 DESCRIPTION

C<CodeString> is a class intended to simplify the process of emitting code
strings.  Ideally this will eventually become a form of "CodeBuffer" that is
more efficient than string concatenation, but for now it works well enough for
me.

The primary method for C<CodeString> objects is C<emit>, which appends a line
(or lines) of code to the string according to a format parameter.  The line can
contain substitution markers (ala printf) that indicate where other parameters
to the call should be placed.

Note that C<CodeString> is just a subclass of Parrot's native C<String> class,
so it's easy to combine CodeString objects with other strings outside of the
C<emit> method.

=head2 Methods

=over 4

=cut

*/

#if PARROT_HAS_ICU
#  include <unicode/uchar.h>
#endif

/* HEADERIZER HFILE: none */
/* HEADERIZER BEGIN: static */
/* HEADERIZER END: static */

pmclass CodeString extends String provides string auto_attrs {
    ATTR PMC *linepos;            /* start of line positions */

/*

=item C<init()>

Initialize the CodeString.

=cut

*/

    VTABLE void init() {
        SUPER();
        SET_ATTR_linepos(INTERP, SELF, PMCNULL);
        PObj_custom_mark_SET(SELF);
    }

/*

=item C<mark()>

Mark the CodeString as live.

=cut

*/

    VTABLE void mark() {
        SUPER();
        if (PMC_data(SELF)) {
            PMC *linepos;

            GET_ATTR_linepos(INTERP, SELF, linepos);

            Parrot_gc_mark_PMC_alive(INTERP, linepos);
        }
    }

/*

=item C<emit(string fmt [, pmc args ] [, pmc hash ])>

Add a line to a C<CodeString> object according to C<fmt>.
The C<fmt> string can contain any number of "%-replacements"
which are replaced by the corresponding values from C<args>
or C<hash> prior to being appended to the string.  (Here
C<args> is a slurpy array, and C<hash> is a slurpy hash.)

The currently defined replacements include:

    %0 %1 ... %9     the value from the args array at index 0..9
    %,               the values of the args array separated by commas
    %%               a percent sign

A percent-sign followed by any other character that is a hash
key receives the value of the hash element.

A newline is automatically added to the end of the fmt.

=cut

*/

  METHOD emit(STRING *fmt, PMC *args :slurpy, PMC *hash :slurpy :named) {
    STRING * const percent     = CONST_STRING(INTERP, "%");
    STRING * const comma       = CONST_STRING(INTERP, ",");
    STRING * const comma_space = CONST_STRING(INTERP, ", ");
    STRING * const newline     = CONST_STRING(INTERP, "\n");
    PMC           *parts       = PMCNULL;
    STRING *key, *repl, *S1;
    INTVAL pos          = 0;
    INTVAL replen       = 0;

    while (pos >= 0) {
        pos += replen;
        pos = Parrot_str_find_index(INTERP, fmt, percent, pos);
        if (pos < 0)
            break;

        key = Parrot_str_substr(INTERP, fmt, pos+1, 1);

        if (VTABLE_exists_keyed_str(INTERP, hash, key)) {
            repl = VTABLE_get_string_keyed_str(INTERP, hash, key);
        }
        else if (Parrot_str_is_cclass(INTERP, enum_cclass_numeric, fmt, (UINTVAL)pos + 1)) {
            const INTVAL I0 = Parrot_str_to_int(INTERP, key);
            repl = VTABLE_get_string_keyed_int(INTERP, args, I0);
        }
        else if (Parrot_str_equal(INTERP, key, comma)) {
            repl = Parrot_str_join(INTERP, comma_space, args);
        }
        else if (Parrot_str_equal(INTERP, key, percent)) {
            repl = percent;
        }
        else {
            /* No substitution is necessary */
            replen = 2;
            continue;
        }

        fmt    = Parrot_str_replace(INTERP, fmt, pos, 2, repl);
        replen = Parrot_str_length(INTERP, repl);
    }

    GET_ATTR_str_val(INTERP, SELF, S1);

    parts = Parrot_pmc_new_init_int(INTERP, enum_class_FixedStringArray, 3);
    VTABLE_set_string_keyed_int(INTERP, parts, 0, S1);
    VTABLE_set_string_keyed_int(INTERP, parts, 1, fmt);

    /* Add a newline if necessary */
    if ('\n' != Parrot_str_indexed(INTERP, fmt, Parrot_str_length(INTERP, fmt) - 1))
        VTABLE_set_string_keyed_int(INTERP, parts, 2, newline);

    S1 = Parrot_str_join(INTERP, STRINGNULL, parts);
    VTABLE_set_string_native(INTERP, SELF, S1);

    RETURN(PMC *SELF);
  }


/*

=item lineof(INTVAL pos)

Return the line number of the line at offset C<pos>.  This code assumes that
the first line is line number zero.

=cut

*/

  METHOD lineof(INTVAL pos) {
    PMC    *linepos;
    INTVAL  count;
    INTVAL  line = 0;

    GET_ATTR_linepos(INTERP, SELF, linepos);

    /* build the linepos array if we haven't already done so */
    if (!linepos || PMC_IS_NULL(linepos)) {
        STRING *str             = NULL;
        INTVAL  eos;
        INTVAL  jpos;

        linepos = Parrot_pmc_new(INTERP, enum_class_ResizableIntegerArray);
        /* get the string itself */
        GET_ATTR_str_val(INTERP, SELF, str);
        eos  = Parrot_str_length(INTERP, str);
        /* find the first newline, if any */
        jpos = Parrot_str_find_cclass(INTERP, enum_cclass_newline,
                                      str, 0, eos);
        while (jpos < eos) {
            ++jpos;
            /* add the start of line position */
            VTABLE_push_integer(INTERP, linepos, jpos);

            /* treat \r\n as a single newline */
            if (jpos < eos
            && string_ord(INTERP, str, jpos - 1)     == 13
            && string_ord(INTERP, str, jpos)         == 10) {
                ++jpos;
            }
            /* search for the next newline */
            jpos = Parrot_str_find_cclass(INTERP, enum_cclass_newline,
                                          str, jpos, eos);
        }
        /* save the array of line positions */
        SET_ATTR_linepos(INTERP, SELF, linepos);
    }

    /* Find the line from the array, stop at the first index that is
     * greater than the position we're looking for.  We do a linear
     * search for now, * perhaps a binary search would be better someday.
     */
    count = VTABLE_elements(INTERP, linepos);
    while (line < count
          && VTABLE_get_integer_keyed_int(INTERP, linepos, line) <= pos)
        ++line;

    RETURN(INTVAL line);
  }


/*

=item C<unique([string fmt])>

Each call to C<unique> returns a unique number, or if a C<fmt>
parameter is given it returns a unique string beginning with
C<fmt>.  (This may eventually be generalized to allow
uniqueness anywhere in the string.)  The function starts
counting at 10 (so that the values 0..9 can be considered "safe").

=cut

*/

  METHOD unique(STRING *format :optional, int has_fmt :opt_flag) {
    static INTVAL counter = 10;
    STRING * const counter_as_string = Parrot_str_from_int(INTERP, counter);
    UNUSED(SELF);

    ++counter;

    if (!has_fmt) {
        RETURN(STRING *counter_as_string);
    }
    else {
        STRING *result = Parrot_str_concat(INTERP, format, counter_as_string);
        RETURN(STRING *result);
    }
  }

/*

=item C<escape(string str)>

Returns an escaped value of C<str> suitable for including in PIR.
If the string contains any non-ASCII characters, then it's
prefixed with 'unicode:'.

=cut

*/

  METHOD escape(STRING *str) {
    STRING *escaped_str  = Parrot_str_escape(INTERP, str);
    STRING * const quote = CONST_STRING(INTERP, "\x22");
    STRING * const x     = CONST_STRING(INTERP, "\\x");
    INTVAL x_pos;
    INTVAL is_unicode = 0;
    UNUSED(SELF);

    escaped_str = Parrot_str_concat(INTERP, quote, escaped_str);
    escaped_str = Parrot_str_concat(INTERP, escaped_str, quote);
    x_pos       = Parrot_str_find_index(INTERP, escaped_str, x, 0);

    if (x_pos != -1) {
        is_unicode = 1;
    }
    else {
        STRING * const u = CONST_STRING(INTERP, "\\u");
        const INTVAL u_pos = Parrot_str_find_index(INTERP, escaped_str, u, 0);
        if (u_pos != -1)
            is_unicode = 1;
    }

    if (is_unicode) {
        STRING * const unicode = CONST_STRING(INTERP, "unicode:");
        escaped_str            = Parrot_str_concat(INTERP, unicode, escaped_str);
    }

    RETURN(STRING *escaped_str);
  }

/*

=item C<charname_to_ord(string name)>

Converts the Unicode character name given by C<name> to its
codepoint value.  Returns -1 if an error occurs in conversion.

=cut

*/


  METHOD charname_to_ord(STRING *name) {
#if PARROT_HAS_ICU
    UErrorCode   err       = U_ZERO_ERROR;
    char * const cstr      = Parrot_str_to_cstring(INTERP, name);
    UChar32      codepoint = u_charFromName(U_EXTENDED_CHAR_NAME, cstr, &err);
    Parrot_str_free_cstring(cstr);
    if (U_SUCCESS(err)) {
        RETURN(INTVAL codepoint);
    }
    RETURN(INTVAL -1);
#else
    Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_LIBRARY_ERROR,
        "no ICU lib loaded");
#endif
  }

/*

=item C<key( string name1 [, string name2, ...] )>

Constructs a PIR key using the strings passed as arguments.
For example, C<key('Foo', 'Bar')> returns C<["Foo";"Bar"]>.

=cut

*/

  METHOD key(PMC *args :slurpy) {
    INTVAL         index;
    const INTVAL   elements      = VTABLE_elements(INTERP, args);
    STRING * const open_bracket  = CONST_STRING(INTERP, "[");
    STRING * const semi          = CONST_STRING(INTERP, ";");
    STRING * const close_bracket = CONST_STRING(INTERP, "]");
    STRING * const s_array       = CONST_STRING(INTERP, "array");
    STRING *       prefix        = NULL;
    STRING *       out           = open_bracket;

    for (index = 0; index < elements; ++index) {
        PMC * const P0 = VTABLE_get_pmc_keyed_int(INTERP, args, index);

        if (!PMC_IS_NULL(P0)) {
            if (VTABLE_does(INTERP, P0, s_array)) {
                const INTVAL elements2 = VTABLE_elements(INTERP, P0);
                INTVAL index2;

                for (index2 = 0; index2 < elements2; ++index2) {
                    STRING *S0   = VTABLE_get_string_keyed_int(INTERP, P0, index2);
                    (STRING *S0) = PCCINVOKE(INTERP, SELF, "escape", STRING *S0);
                    if (prefix)
                        out = Parrot_str_concat(INTERP, out, prefix);
                    out    = Parrot_str_concat(INTERP, out, S0);
                    prefix = semi;
                }
            }
            else {
                STRING *S0   = VTABLE_get_string_keyed_int(INTERP, args, index);
                (STRING *S0) = PCCINVOKE(INTERP, SELF, "escape", STRING *S0);
                if (prefix)
                    out = Parrot_str_concat(INTERP, out, prefix);
                out    = Parrot_str_concat(INTERP, out, S0);
                prefix = semi;
            }
        }
    }

    out = Parrot_str_concat(INTERP, out, close_bracket);

    RETURN(STRING *out);
}

/*

=back

=cut

*/

}


/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
