text: use v5;
  
  package Perlito5::JavaScript3::Sprintf;
  
  sub emit_javascript3 {
  
      return <<'EOT';
  /**
   * Copyright (c) 2010 Jakob Westhoff
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   * 
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   * 
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  CORE.sprintf = function(List__) {
      var format = List__[0];
      var list = List__[1];
  
      // Check for format definition
      if ( typeof format != 'string' ) {
          CORE.die(["sprintf: The first arguments need to be a valid format string."]);
      }
      
      /**
       * Define the regex to match a formating string
       * The regex consists of the following parts:
       * percent sign to indicate the start
       * (optional) sign specifier
       * (optional) padding specifier
       * (optional) alignment specifier
       * (optional) width specifier
       * (optional) precision specifier
       * type specifier:
       *  % - literal percent sign
       *  b - binary number
       *  c - ASCII character represented by the given value
       *  d - signed decimal number
       *  f - floating point value
       *  o - octal number
       *  s - string
       *  x - hexadecimal number (lowercase characters)
       *  X - hexadecimal number (uppercase characters)
       */
      var r = new RegExp( /%(\+)?([0 ]|'(.))?(-)?([0-9]+)?(\.([0-9]+))?([%bcdfosxX])/g );
  
      /**
       * Each format string is splitted into the following parts:
       * 0: Full format string
       * 1: sign specifier (+)
       * 2: padding specifier (0/<space>/'<any char>)
       * 3: if the padding character starts with a ' this will be the real 
       *    padding character
       * 4: alignment specifier
       * 5: width specifier
       * 6: precision specifier including the dot
       * 7: precision specifier without the dot
       * 8: type specifier
       */
      var parts      = [];
      var paramIndex = 0;
      while ( part = r.exec( format ) ) {
          // Check if an input value has been provided, for the current
          // format string
          if ( paramIndex >= list.length ) {
              CORE.die(["sprintf: At least one argument was missing."]);
          }
  
          parts[parts.length] = {
              /* beginning of the part in the string */
              begin: part.index,
              /* end of the part in the string */
              end: part.index + part[0].length,
              /* force sign */
              sign: ( part[1] == '+' ),
              /* is the given data negative */
              negative: ( parseInt( list[paramIndex] ) < 0 ) ? true : false,
              /* padding character (default: <space>) */
              padding: ( part[2] == undefined )
                       ? ( ' ' ) /* default */
                       : ( ( part[2].substring( 0, 1 ) == "'" ) 
                           ? ( part[3] ) /* use special char */
                           : ( part[2] ) /* use normal <space> or zero */
                         ),
              /* should the output be aligned left?*/
              alignLeft: ( part[4] == '-' ),
              /* width specifier (number or false) */
              width: ( part[5] != undefined ) ? part[5] : false,
              /* precision specifier (number or false) */
              precision: ( part[7] != undefined ) ? part[7] : false,
              /* type specifier */
              type: part[8],
              /* the given data associated with this part converted to a string */
              data: ( part[8] != '%' ) ? String ( list[paramIndex++] ) : false
          };
      }
  
      var newString = "";
      var start = 0;
      // Generate our new formated string
      for( var i=0; i<parts.length; ++i ) {
          // Add first unformated string part
          newString += format.substring( start, parts[i].begin );
          
          // Mark the new string start
          start = parts[i].end;
  
          // Create the appropriate preformat substitution
          // This substitution is only the correct type conversion. All the
          // different options and flags haven't been applied to it at this
          // point
          var preSubstitution = "";
          switch ( parts[i].type ) {
              case '%':
                  preSubstitution = "%";
              break;
              case 'b':
                  preSubstitution = Math.abs( parseInt( parts[i].data ) ).toString( 2 );
              break;
              case 'c':
                  preSubstitution = String.fromCharCode( Math.abs( parseInt( parts[i].data ) ) );
              break;
              case 'd':
                  preSubstitution = String( Math.abs( parseInt( parts[i].data ) ) );
              break;
              case 'f':
                  preSubstitution = ( parts[i].precision == false )
                                    ? ( String( ( Math.abs( parseFloat( parts[i].data ) ) ) ) )
                                    : ( Math.abs( parseFloat( parts[i].data ) ).toFixed( parts[i].precision ) );
              break;
              case 'o':
                  preSubstitution = Math.abs( parseInt( parts[i].data ) ).toString( 8 );
              break;
              case 's':
                  preSubstitution = parts[i].data.substring( 0, parts[i].precision ? parts[i].precision : parts[i].data.length ); /* Cut if precision is defined */
              break;
              case 'x':
                  preSubstitution = Math.abs( parseInt( parts[i].data ) ).toString( 16 ).toLowerCase();
              break;
              case 'X':
                  preSubstitution = Math.abs( parseInt( parts[i].data ) ).toString( 16 ).toUpperCase();
              break;
              default:
                  throw 'sprintf: Unknown type "' + parts[i].type + '" detected. This should never happen. Maybe the regex is wrong.';
          }
  
          // The % character is a special type and does not need further processing
          if ( parts[i].type ==  "%" ) {
              newString += preSubstitution;
              continue;
          }
  
          // Modify the preSubstitution by taking sign, padding and width
          // into account
  
          // Pad the string based on the given width
          if ( parts[i].width != false ) {
              // Padding needed?
              if ( parts[i].width > preSubstitution.length ) 
              {
                  var origLength = preSubstitution.length;
                  for( var j = 0; j < parts[i].width - origLength; ++j ) 
                  {
                      preSubstitution = ( parts[i].alignLeft == true ) 
                                        ? ( preSubstitution + parts[i].padding )
                                        : ( parts[i].padding + preSubstitution );
                  }
              }
          }
  
          // Add a sign symbol if neccessary or enforced, but only if we are
          // not handling a string
          if ( parts[i].type == 'b' 
            || parts[i].type == 'd' 
            || parts[i].type == 'o' 
            || parts[i].type == 'f' 
            || parts[i].type == 'x' 
            || parts[i].type == 'X' ) {
              if ( parts[i].negative == true ) {
                  preSubstitution = "-" + preSubstitution;
              }
              else if ( parts[i].sign == true ) {
                  preSubstitution = "+" + preSubstitution;
              }
          }
  
          // Add the substitution to the new string
          newString += preSubstitution;
      }
  
      // Add the last part of the given format string, which may still be there
      newString += format.substring( start, format.length );
  
      return newString;
  };
  
  CORE.printf = function(List__) {
      return CORE.print([ CORE.sprintf(List__) ]);
  };
  
  EOT
  } # end of emit_javascript3()
  
  1;
  
  