/*
 * Copyright (c) 2004, 2005 James Bailie.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * The name of James Bailie may not be used to endorse or promote
 * products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "runtime.h"

int serv_fd;

void sigchld_handler( int signo )
{
   while( waitpid( -1, NULL, WNOHANG ) > 0 )
      ;
}

/*
 * Allocation.
 */

void *memory( int size )
{
   void *ptr;

   if ( size == 0 )
      return NULL;

   if (( ptr = malloc( size )) == NULL )
   {
      fprintf( stderr, "memory: malloc: %s.\n", strerror( errno ));
      exit( 1 );
   }

   return ptr;
}

char *str_dup( char *str, int len )
{
   char *dst, *src, *ptr;
   int i;

   ptr = ( char *)memory( len + 1 );

   for( src = str, dst = ptr, i = len; i; --i )
      *dst++ = *src++;

   *dst = '\0';

   return ptr;
}

/*
 * Tables.
 */

struct hash_elt **make_table()
{
   struct hash_elt **table, **ptr;
   int i;

   table = memory( sizeof( struct hash_elt * ) * HASH_SIZE );

   for( i = 0, ptr = table; i < HASH_SIZE; ++i )
      *ptr++ = NULL;

   return table;
}

void insert_elt( struct hash_elt **hash, struct atom *id, struct atom *elt )
{
   int key;
   struct hash_elt *ptr = NULL, *ptr2 = NULL;

   key = abs( ( int )( ( int )id + HASH_SIZE ) % HASH_SIZE );

   if ( hash[ key ] == NULL )
   {
      hash[ key ] = memory( sizeof( struct hash_elt ));
      hash[ key ]->element = elt;
      hash[ key ]->next = NULL;
      hash[ key ]->binding = id;
      return;
   }

   ptr2 = hash[ key ];

   for( ptr = ptr2; ptr != NULL; ptr = ptr->next )
      if ( ptr->binding == id )
         break;
      else
         ptr2 = ptr;

   if ( ptr == NULL )
   {
      ptr2->next = ( struct hash_elt *)memory( sizeof( struct hash_elt ));
      ptr = ptr2->next;
      ptr->next = NULL;
   }

   ptr->element = elt;
   ptr->binding = id;
}

struct atom *lookup_elt( struct hash_elt **hash, struct atom *id )
{
   int key;
   struct hash_elt *ptr;

   key = abs( ( int )( ( int )id + HASH_SIZE ) % HASH_SIZE );

   if ( hash[ key ] == NULL )
      return empty_string_atom;

   ptr = hash[ key ];

   do
   {
      if ( ptr->binding == id )
         return ptr->element;

      ptr = ptr->next;
   }
   while( ptr != NULL );

   return empty_string_atom;
}

void remove_elt( struct hash_elt **hash, struct atom *id )
{
   int key;
   struct hash_elt *ptr, *ptr2;

   key = abs( ( int )( ( int )id + HASH_SIZE ) % HASH_SIZE );

   if ( hash[ key ] == NULL )
      return;

   ptr = hash[ key ];
   ptr2 = NULL;

   do
   {
      if ( ptr->binding == id )
         break;

      ptr2 = ptr;
      ptr = ptr->next;
   }
   while( ptr != NULL );

   if ( ptr == NULL )
      return;

   if ( ptr2 == NULL )
      hash[ key ] = ptr->next;
   else
      ptr2->next = ptr->next;

   free( ptr );
}

/*
 * Stacks.
 */

struct stack *make_stack()
{
   struct stack *a;

   a = ( struct stack *)memory( sizeof( struct stack ));
   a->values = memory( sizeof( void * ) * stack_inc );
   a->free = stack_inc;
   a->used = 0;
   a->top = a->values;

   return a;
}

void stack_free( struct stack *a )
{
   free( a->values );
   free( a );
}

void stack_push( struct stack *a, struct atom *o )
{
   if ( a->free == 0 )
   {
      a->values = realloc( a->values, sizeof( void * ) * ( a->used + stack_inc ) );

      if ( a->values == NULL )
      {
         fprintf( stderr, "stack_push: realloc: %s.\n", strerror( errno ));
         exit( 1 );
      }

      a->free = stack_inc;
      a->top = &a->values[ a->used - 1 ];
   }

   if ( a->used )
      ++a->top;

   *a->top = o;
   --a->free;
   ++a->used;
}

struct atom *stack_pop( struct stack *a )
{
   struct atom *ptr;

   if ( a->used )
   {
      ptr = *a->top;
      --a->used;
      ++a->free;

      if ( a->used )
         --a->top;

      return ptr;
   }

   return NULL;
}

void stack_truncate( struct stack *a, int i )
{
   while( i-- )
      stack_pop( a );
}

void stack_clear( struct stack *s )
{
   s->free += s->used;
   s->top = s->values;
   s->used = 0;
}

/*
 * Strings.
 */

void string_free( struct string *s )
{
   free( s->str );
   free( s );
}

struct string *make_string()
{
   struct string *s;

   s = ( struct string *)memory( sizeof( struct string ));
   s->str = ( char *)memory( stack_inc );
   *s->str = '\0';

   /* Leave room for end-of-string sentinel. */

   s->free = stack_inc - 1;
   s->used = 0;
   s->top = s->str;

   return s;
}

void string_prepend( struct string *s, char c )
{
   char *ptr, *ptr2;

   if ( s->used == 0 )
   {
      string_append( s, c );
      return;
   }

   if ( s->free == 0 )
   {
      s->str = ( char *)realloc( s->str, s->used + 1 + stack_inc );

      if ( s->str == NULL )
      {
         fprintf( stderr, "string_prepend: realloc: %s.\n", strerror( errno ));
         exit( 1 );
      }

      /* Leave room for end-of-string sentinel. */

      s->free = stack_inc - 1;
      s->top = &s->str[ s->used ];
   }

   ptr2 = &s->str[ s->used + 1 ];

   for( ptr = &s->str[ s->used ]; ptr >= s->str; --ptr )
      *ptr2-- = *ptr;

   s->str[ 0 ] = c;

   ++s->used;
   ++s->top;
   --s->free;
}

void string_append( struct string *s, char c )
{
   if ( s->free == 0 )
   {
      s->str = ( char *)realloc( s->str, s->used + 1 + stack_inc );

      if ( s->str == NULL )
      {
         fprintf( stderr, "string_append: realloc: %s.\n", strerror( errno ));
         exit( 1 );
      }

      /* Leave room for end-of-string sentinel */

      s->free = stack_inc - 1;
      s->top = &s->str[ s->used ];
   }

   ++s->used;
   --s->free;
   *s->top++ = c;
   *s->top = '\0';
}

void string_erase( struct string *s, int idx )
{
   char *ptr, *ptr2;

   ptr = &s->str[ idx ];
   ptr2 = &s->str[ idx + 1 ];

   while( *ptr2 )
      *ptr++ = *ptr2++;

   *ptr = '\0';

   --s->used;
   ++s->free;
   --s->top;
}

void string_chop( struct string *s )
{
   if ( s->used )
   {
      *--s->top = '\0';
      --s->used;
      ++s->free;
   }
}

void string_clear( struct string *s )
{
   if ( s->used )
   {
      s->free += s->used;
      s->used = 0;
      *s->str = '\0';
      s->top = s->str;
   }
}

void string_assign( struct string *s, char *c, int len )
{
   string_clear( s );
   while ( len-- )
      string_append( s, *c++ );
}

/*
 * Atoms.
 */

void free_data( struct atom *atom )
{
   free( atom->syntax );

   switch( type( atom->flags ))
   {
      case ATOM_STRING:
         break;

      case ATOM_REGEXP:
         regfree( atom->data.regexp );
         free( atom->data.regexp );
         break;

      case ATOM_CLOSURE:
         free( atom->data.closure );
         break;

      case ATOM_STACK:
         stack_free( atom->data.stack );
         break;

      case ATOM_TABLE:
         free( atom->data.table );
         break;
   }
}

void remove_atom( char *name, int len )
{
   int key, i;
   char *nptr;
   struct hash_elt *ptr, *ptr2;

   for( key = 0, i = len, nptr = name; i; --i )
      key += *nptr++;

   key = abs(( key + HASH_SIZE ) % HASH_SIZE );

   if ( atoms[ key ] == NULL )
      return;

   ptr = atoms[ key ];
   ptr2 = NULL;

   do
   {
      if ( ptr->element->syntax == name )
         break;

      ptr2 = ptr;
      ptr = ptr->next;
   }
   while( ptr != NULL );

   if ( ptr == NULL )
      return;

   if ( ptr2 == NULL )
      atoms[ key ] = ptr->next;
   else
      ptr2->next = ptr->next;

   free( ptr );
}

struct atom *get_atom( char *name, int len, int duplicate )
{
   int key, i;
   char *nptr;
   struct atom *atom;
   struct hash_elt *ptr = NULL, *ptr2 = NULL;

   for( key = 0, i = len, nptr = name; i; --i )
      key += *nptr++;

   key = abs( ( key + HASH_SIZE ) % HASH_SIZE );

   if ( atoms[ key ] == NULL )
   {
      atom = make_atom();
      atom->syntax = ( duplicate ? str_dup( name, len ) : name );
      atom->flags = len << 8;

      atoms[ key ] = memory( sizeof( struct hash_elt ));
      atoms[ key ]->element = atom;
      atoms[ key ]->next = NULL;

      return atom;
   }

   ptr2 = atoms[ key ];

   for( ptr = ptr2; ptr != NULL; ptr = ptr->next )
   {
      atom = ptr->element;

      if ( len == length( atom->flags ))
      {
         char *ptr3 = name, *ptr4 = atom->syntax;
         int idx = len;

         while( idx-- )
            if ( *ptr3++ != *ptr4++ )
               break;

         if ( idx < 0 )
            return atom;
      }

      ptr2 = ptr;
   }

   ptr2->next = ( struct hash_elt *)memory( sizeof( struct hash_elt ));
   ptr = ptr2->next;
   ptr->next = NULL;

   atom = make_atom();
   atom->syntax = str_dup( name, len );
   atom->flags = len << 8;

   ptr->element = atom;

   return atom;
}

struct atom *make_atom()
{
   if ( reclaimed_atoms->used )
      return ( struct atom *)stack_pop( reclaimed_atoms );

   if ( atom_pool_free == 0 )
   {
      stack_push( atom_pool_stack, ( void *)atom_pool );
      atom_pool = ( struct atom *)memory( sizeof( struct atom ) * POOL_INC );
      atom_pool_ptr = atom_pool;
      bzero( atom_pool, POOL_INC );
      atom_pool_free = POOL_INC;
   }

   --atom_pool_free;

   bzero( atom_pool_ptr, sizeof( struct atom ) );

   return atom_pool_ptr++;
}

void hash_free( struct hash_elt **hash )
{
   int i;
   struct hash_elt **ptr, *ptr2, *ptr3;

   ptr = hash;

   for( i = 0; i < HASH_SIZE; ++i )
   {
      if ( *ptr == NULL )
      {
         ++ptr;
         continue;
      }

      ptr2 = *ptr;

      do
      {
         ptr3 = ptr2->next;
         free( ptr2 );
         ptr2 = ptr3;
      }
      while( ptr2 != NULL );

      ++ptr;
   }
}

void free_atom_data()
{
   struct atom *top;
   int i, j;

   i = atom_pool_stack->used;

   while( i )
   {
      top = ( struct atom *)atom_pool_stack->values[ --i ];

      for( j = 0; j < POOL_INC; ++j, ++top )
         if ( numberp( top ) == 0 && top->flags )
            free_data( top );
   }

   for( top = atom_pool; top < atom_pool_ptr; ++top )
      if ( numberp( top ) == 0 && top->flags )
         free_data( top );
}

void free_atoms()
{
   struct atom *ptr;

   free_atom_data();
   free( atom_pool );

   while(( ptr = ( struct atom *)stack_pop( atom_pool_stack )) != NULL )
      free( ptr );

   stack_free( atom_pool_stack );
}

struct atom *make_atom_from_table( struct hash_elt **table )
{
   static int counter = 0;
   char buffer[ 32 ];
   int len;
   struct atom *atom;

   snprintf( buffer, sizeof( buffer ), "<TABLE#%d>", counter++ );
   len = strlen( buffer );

   atom = make_atom();
   atom->syntax = str_dup( buffer, len );
   atom->flags = len << 8;
   atom->flags |= ATOM_TABLE;

   atom->data.table = table;

   return atom;
}

struct atom *make_atom_from_stack( struct stack *s )
{
   static int counter = 0;
   char buffer[ 32 ];
   int len;
   struct atom *atom;

   snprintf( buffer, sizeof( buffer ), "<STACK#%d>", counter++ );
   len = strlen( buffer );

   atom = make_atom();
   atom->syntax = str_dup( buffer, len );
   atom->flags = len << 8;
   atom->flags |= ATOM_STACK;

   atom->data.stack = s;

   return atom;
}

struct atom *make_atom_from_record( struct atom **a )
{
   static int counter = 0;
   char buffer[ 32 ];
   int len;
   struct atom *atom;

   snprintf( buffer, sizeof( buffer ), "<RECORD#%d>", counter++ );
   len = strlen( buffer );

   atom = make_atom();
   atom->syntax = str_dup( buffer, len );
   atom->flags = len << 8;
   atom->flags |= ATOM_RECORD;

   atom->data.record = a;

   return atom;
}

struct atom *make_atom_from_string( char *s, int len, int duplicate )
{
   struct atom *entry;

   if ( len == -1 )
      len = strlen( s );

   entry = get_atom( s, len, duplicate );
   entry->flags |= ATOM_STRING;

   return entry;
}

struct atom *make_atom_from_regexp( regex_t *regexp )
{
   static int counter = 0;
   char buffer[ 32 ];
   int len;
   struct atom *atom;

   snprintf( buffer, sizeof( buffer ), "<REGEXP#%d>", counter++ );
   len = strlen( buffer );

   atom = make_atom();
   atom->syntax = str_dup( buffer, len );
   atom->flags = len << 8;
   atom->flags |= ATOM_REGEXP;

   atom->data.regexp = regexp;

   return atom;
}

/*
 * Closures.
 */

void make_closure( void ( *func )(), int args )
{
   struct atom *atom;

   atom = make_atom();
   atom->syntax = str_dup( "<CLOSURE>", 9 );
   atom->flags = 9 << 8;
   atom->flags |= ATOM_CLOSURE;

   atom->data.closure = memory( sizeof( struct atom * ) * ( args + 2 ));
   atom->data.closure[ 0 ] = ( struct atom *)func;
   atom->data.closure[ args + 1 ] = NULL;

   while( args )
      atom->data.closure[ args-- ] = stack_pop( stack );

   stack_push( stack, atom );
}

/*
 * Startup
 */

void initialize( char **argv, int argc )
{
   signal( SIGCHLD, sigchld_handler );

   srandomdev();
   stack_inc = 12;
   exit_status = 0;

   stack = make_stack();

   globals = memory( sizeof( struct atom * ) * num_globals );

   reclaimed_atoms = make_stack();
   atom_pool_stack = make_stack();

   atom_pool = ( struct atom *)memory( sizeof( struct atom ) * POOL_INC );
   atom_pool_free = POOL_INC;
   atom_pool_ptr = atom_pool;

   bzero( atoms, sizeof( struct hash_elt * ) * HASH_SIZE );

   working_string = make_string();
   private_string = make_string();
   working_stack = make_stack();

   empty_string_atom = make_atom_from_string( "", 0, 1 );

   first_arg = arg_ptr = argv;
   last_arg = &argv[ argc - 1 ];

   descriptors[ 0 ] = make_stack();
   descriptors[ 1 ] = make_stack();
   descriptors[ 2 ] = make_stack();

   serv_fd = -1;
}

void resume_descriptor( int arg1 )
{
   int fd;

   fd = ( int )stack_pop( descriptors[ arg1 ] );

   switch( arg1 )
   {
      case 0:
         if ( stdin != NULL )
            fclose( stdin );
         break;

      case 1:
         if ( stdout != NULL )
            fclose( stdout );
         break;

      case 2:
         if ( stderr != NULL )
            fclose( stderr );
         break;
   }

   if ( dup2( fd, arg1 ) < 0 )
   {
      fprintf( stderr, "dup2: %s.\n", strerror( errno ));
      close( fd );
      exit( 1 );
   }

   close( fd );

   switch( arg1 )
   {
      case 0:
         stdin = fdopen( arg1, "r" );
         break;

      case 1:
         stdout = fdopen( arg1, "w" );
         break;

      case 2:
         stderr = fdopen( arg1, "w" );
         break;
   }

   *stack->top = toptr( 1 );
}

/*
 * Shutdown
 */

void cleanup()
{
   hash_free( atoms );
   free_atoms();

   stack_free( stack );
   stack_free( reclaimed_atoms );

   stack_free( working_stack );
   string_free( working_string );
   string_free( private_string );

   free( globals );

   {
      int i;

      for( i = 0; i < 3; ++i )
         while( descriptors[ i ]->used )
            resume_descriptor( i );
   }
}

/*
 * GC
 */

void sweep_atoms()
{
   struct atom *top;
   int i, j, old_stack_inc;

   reclaimed_atoms->free += reclaimed_atoms->used;
   reclaimed_atoms->used = 0;
   reclaimed_atoms->top = reclaimed_atoms->values;

   old_stack_inc = stack_inc;
   stack_inc = POOL_INC;

   i = atom_pool_stack->used;

   while( i )
   {
      top = ( struct atom *)atom_pool_stack->values[ --i ];

      for( j = 0; j < POOL_INC; ++j, ++top )
         if ( numberp( top ) == 0 )
         {
            if ( ismarked( top->flags ) )
               unmark( top->flags );
            else
            {
               if ( type( top->flags ) == ATOM_STRING )
                  remove_atom( top->syntax, length( top->flags ));

               free_data( top );
               bzero( top, sizeof( struct atom ));
               stack_push( reclaimed_atoms, top );
            }
   		}
   }

   for( top = atom_pool; top < atom_pool_ptr; ++top )
   {
      if ( numberp( top ) == 0 )
      {
         if ( ismarked( top->flags ) )
            unmark( top->flags );
         else
         {
            if ( type( top->flags ) == ATOM_STRING )
               remove_atom( top->syntax, length( top->flags ));

            free_data( top );
            bzero( top, sizeof( struct atom ));
            stack_push( reclaimed_atoms, top );
         }
   	}
   }

   stack_inc = old_stack_inc;
}

struct stack *get_hash_keys( struct hash_elt **table )
{
   struct stack *stk;
   struct hash_elt *ptr, **ptr2;
   int i;

   stk = make_stack();
   ptr2 = table;

   for( i = 0; i < HASH_SIZE; ++i )
   {
      ptr = *ptr2++;

      while( ptr != NULL )
      {
         stack_push( stk, ptr->binding );
         ptr = ptr->next;
      }
   }

   return stk;
}

struct stack *get_hash_values( struct hash_elt **table )
{
   struct stack *stk;
   struct hash_elt *ptr, **ptr2;
   int i;

   stk = make_stack();
   ptr2 = table;

   for( i = 0; i < HASH_SIZE; ++i )
   {
      ptr = *ptr2++;

      while( ptr != NULL )
      {
         stack_push( stk, ptr->element );
         ptr = ptr->next;
      }
   }

   return stk;
}

void mark_table( struct atom *atom )
{
   struct stack *values;

   values = get_hash_keys( atom->data.table );

   while( values->used )
      mark_atom( stack_pop( values ) );

   stack_free( values );

   values = get_hash_values( atom->data.table );

   while( values->used )
      mark_atom( stack_pop( values ) );

   stack_free( values );
}

void mark_stack( struct atom *atom )
{
   struct atom **ptr;

   if ( atom->data.stack->used )
      for( ptr = atom->data.stack->values; ptr <= atom->data.stack->top; ++ptr )
         mark_atom( *ptr );
}

void mark_closure( struct atom *closure )
{
   struct atom **ptr;

   for( ptr = &( closure->data.closure[ 1 ] ); *ptr != NULL; ++ptr )
      mark_atom( *ptr );
}

void mark_atom( struct atom *ptr )
{
   if ( numberp( ptr ) )
      return;

   mark( ptr->flags );

   switch( type( ptr->flags ) )
   {
      case ATOM_CLOSURE:
         mark_closure( ptr );
         break;

      case ATOM_STACK:
         mark_stack( ptr );
         break;

      case ATOM_TABLE:
         mark_table( ptr );
         break;
   }
}

void mark_atoms()
{
   struct atom **ptr;
   int i;

   mark( empty_string_atom->flags );

   if ( stack->used )
      for( ptr = stack->values; ptr <= stack->top; ++ptr )
         mark_atom( *ptr );

   for( ptr = globals, i = 0; i < num_globals; ++ptr, ++i )
      if ( *ptr != NULL )
         mark_atom( *ptr );
}

void collect_garbage()
{
   mark_atoms();
   sweep_atoms();
}

/*
 * Trampoline.
 */

int main( int argc, char **argv )
{
   int gc = GC_FREQUENCY;

   initialize( argv, argc );
   atexit( cleanup );

   next = FUNCTION_0;

   while( next != NULL )
   {
      next();

      if ( --gc == 0 )
      {
         collect_garbage();
         gc = GC_FREQUENCY;
      }
   }

   return exit_status;
}

/*
 * The non-macro intrinsics begin here and continue to the end of the file.
 */

void readchars()
{
   char *buffer;
   int result;

   buffer = memory( number( *stack->top ) );

   if (( result = fread( buffer, number( *stack->top ), 1, stdin )) <= 0 )
   {
      free( buffer );

      if ( feof( stdin ) )
      {
         *stack->top = toptr( 0 );
         return;
      }
      else
      {
         fprintf( stderr, "read: fread: %s.\n", strerror( errno ));
         exit( 1 );
      }
   }

   *stack->top = make_atom_from_string( buffer, result, 0 );
}

void join( int stk )
{
   struct atom **atom;
   char *sep, *ptr;
   int len, i;

   stack_clear( working_stack );

   if ( stk )
   {
      for( i = ( *stack->top )->data.stack->used - 1; i >= 0; --i )
         stack_push( working_stack, ( *stack->top )->data.stack->values[ i ] );
      stack_pop( stack );
   }
   else
   {
      i = number( stack_pop( stack ));

      while( i-- )
         stack_push( working_stack, stack_pop( stack ));
   }

   sep = ( *working_stack->top )->syntax;
   len = length( ( *working_stack->top )->flags );

   string_clear( working_string );

   for( atom = working_stack->top - 1; atom > working_stack->values; --atom )
   {
      i = length( ( *atom )->flags );
      ptr = ( *atom )->syntax;

      while( i-- )
         string_append( working_string, *ptr++ );

      i = len;
      ptr = sep;

      while( i-- )
         string_append( working_string, *ptr++ );
   }

   i = length( ( *atom )->flags );
   ptr = ( *atom )->syntax;

   while( i-- )
      string_append( working_string, *ptr++ );

   stack_push( stack, make_atom_from_string( working_string->str, working_string->used, 1 ));
}

void split()
{
   char *input, *next, *delim;
   struct atom *atom;
   struct stack *output;
   int limit;

   limit = number( stack_pop( stack ));

   atom = stack_pop( stack );
   string_assign( working_string, atom->syntax, length( atom->flags ));

   delim = ( *stack->top )->syntax;
   output = make_stack();

   input = working_string->str;

   if ( limit )
   {
      while( --limit && input != NULL )
      {
         next = strsep( &input, delim );
         stack_push( output, make_atom_from_string( next, -1 , 1));
      }

      if ( input != NULL )
         stack_push( output, make_atom_from_string( input, -1 , 1 ));

      if ( input == NULL && output->used == 0 )
         stack_push( output, atom );
   }

   *stack->top = make_atom_from_stack( output );
}

int compare_strings( const void *a, const void *b )
{
   return strcmp( ( *( struct atom **)a )->syntax, ( *( struct atom **)b )->syntax );
}

int compare_numbers( const void *a, const void *b )
{
   return ( number( *( int *)a ) - number( *( int *)b ) );
}

void exec( int stk )
{
   struct atom **ptr;

   stack_clear( working_stack );

   if ( stk )
   {
      struct stack *stk;

      stk = ( stack_pop( stack ) )->data.stack;

      for( ptr = stk->values; ptr <= stk->top; ++ptr )
         stack_push( working_stack, ( struct atom *)( *ptr )->syntax );
   }
   else
   {
      int args;

      args = number( stack_pop( stack ));

      for( ptr = ( stack->top - args ) + 1; ptr <= stack->top; ++ptr )
         stack_push( working_stack, ( struct atom *)( *ptr )->syntax );
   }

   stack_push( working_stack, NULL );
   execvp( ( char *)working_stack->values[ 0 ], ( char **)&working_stack->values[ 0 ] );
}

void shift()
{
   int i, top;
   struct atom *result;

   if ( ( *stack->top )->data.stack->used )
   {
      result = ( *stack->top )->data.stack->values[ 0 ];
      top = ( *stack->top )->data.stack->used - 1;

      for( i = 0; i < top; ++i )
         ( *stack->top )->data.stack->values[ i ] =
            ( *stack->top )->data.stack->values[ i + 1 ];

      if ( --( *stack->top )->data.stack->used )
         --( *stack->top )->data.stack->top;
      ++( *stack->top )->data.stack->free;

      *stack->top = result;
   }
   else
      *stack->top = empty_string_atom;
}

void unshift()
{
   struct atom *new;
   int i;

   new = stack_pop( stack );

   stack_push( ( *stack->top )->data.stack, NULL );

   for( i = ( *stack->top )->data.stack->used - 1; i; --i )
      ( *stack->top )->data.stack->values[ i ] =
         ( *stack->top )->data.stack->values[ i - 1 ];

   ( *stack->top )->data.stack->values[ 0 ] = new;
}

void regexp_comp()
{
   char *ptr, *tmp;
   int len, escape = 0;

   ptr = ( *stack->top )->syntax;
   len = length( ( *stack->top )->flags );
   string_clear( working_string );

   for( ; len; --len )
   {
      if ( *ptr == '\\' )
      {
         escape ^= 1;

         if ( !escape )
         {
            string_append( working_string, '\\' );
            string_append( working_string, '\\' );
         }

         ++ptr;
         continue;
      }

      if ( escape )
      {
         switch( *ptr )
         {
            case 'b':
               string_append( working_string, ' ' );
               break;

            case 't':
               string_append( working_string, '\t' );
               break;

            case 'n':
               string_append( working_string, '\n' );
               break;

            case '>':
               tmp = "[[:>:]]";
               while( *tmp )
                  string_append( working_string, *tmp++ );
               break;

            case '<':
               tmp = "[[:<:]]";
               while( *tmp )
                  string_append( working_string, *tmp++ );
               break;

            case '?':
               string_append( working_string, '\\' );
               string_append( working_string, '?' );
               break;

            case '+':
               string_append( working_string, '\\' );
               string_append( working_string, '+' );
               break;

            case '^':
               string_append( working_string, '\\' );
               string_append( working_string, '^' );
               break;

            case '$':
               string_append( working_string, '\\' );
               string_append( working_string, '$' );
               break;

            case '.':
               string_append( working_string, '\\' );
               string_append( working_string, '.' );
               break;

            case '[':
               string_append( working_string, '\\' );
               string_append( working_string, '[' );
               break;

            case '(':
               string_append( working_string, '\\' );
               string_append( working_string, '(' );
               break;

            case ')':
               string_append( working_string, '\\' );
               string_append( working_string, ')' );
               break;

            case '|':
               string_append( working_string, '\\' );
               string_append( working_string, '|' );
               break;

            case '{':
               string_append( working_string, '\\' );
               string_append( working_string, '{' );
               break;

            case '*':
               string_append( working_string, '\\' );
               string_append( working_string, '*' );
               break;

            default:
               string_append( working_string, *ptr );
         }
      }
      else
         string_append( working_string, *ptr );

      ++ptr;
      escape = 0;
   }

   {
      int result;
      regex_t *regexp;

      regexp = memory( sizeof( regex_t ));
      regexp->re_endp = &working_string->str[ working_string->used ];
      result = regcomp( regexp, working_string->str, REG_EXTENDED | REG_PEND );

      if ( result )
      {
         char err[ 80 ];

         regerror( result, regexp, err, sizeof( err ));
         free( regexp );

         *stack->top = make_atom_from_string( err, -1, 1 );
      }
      else
         *stack->top = make_atom_from_regexp( regexp );
   }
}

struct stack *apply_regexp( regex_t *regexp, char *the_string, int len, int show_offset )
{
   struct stack *s;
   regmatch_t matches[ 20 ];
   int result, length, i;

   matches[ 0 ].rm_so = 0;
   matches[ 0 ].rm_eo = len;

   result = regexec( regexp, the_string, ( show_offset == 2 ? 1 : 20 ), matches, REG_STARTEND );

   if ( result )
   {
      char err[ 80 ];

      if ( result == REG_NOMATCH )
         return NULL;

      regerror( result, regexp, err, sizeof( err ));
      fprintf( stderr, "apply_regexp: regexec: %s.\n", err );
      return NULL;
   }

   s = make_stack();

   if ( show_offset )
   {
      stack_push( s, toptr( ( int )matches[ 0 ].rm_so ));
      stack_push( s, toptr( ( int )matches[ 0 ].rm_eo ));

      if ( show_offset == 2 )
         return s;
   }

   for( i = 0; i < 20; ++i )
   {
      if ( matches[ i ].rm_so >= 0 )
      {
         int j;
         char *frag;

         string_clear( working_string );
         length = matches[ i ].rm_so + matches[ i ].rm_eo - matches[ i ].rm_so;
         frag = &the_string[ matches[ i ].rm_so ];

         for( j = matches[ i ].rm_so; j < length; ++j )
            string_append( working_string, *frag++ );

         stack_push( s, make_atom_from_string( working_string->str, working_string->used, 1 ));
      }
      else
         stack_push( s, empty_string_atom );
   }

   return s;
}

void regexp_match( int substrings )
{
   struct atom *atom;
   struct stack *result;

   atom = stack_pop( stack );

   if (( result = apply_regexp( ( *stack->top )->data.regexp, atom->syntax,
                                length( atom->flags ),
                                ( substrings ? 0 : 2 ))) == NULL )
      *stack->top = toptr( 0 );
   else
      *stack->top = make_atom_from_stack( result );
}

int add_char( char *ptr, int change_case )
{
   switch( change_case )
   {
      case 0:
         string_append( private_string, *ptr );
         break;

      case 1:
         string_append( private_string, toupper( *ptr ));
         change_case = 0;
         break;

      case 2:
         string_append( private_string, toupper( *ptr ));
         break;

      case 3:
         string_append( private_string, tolower( *ptr ));
         change_case = 0;
         break;

      case 4:
         string_append( private_string, tolower( *ptr ));
         break;
   }

   return change_case;
}

void regexp_substitute()
{
   char *the_string, *replacement, *tmp1, *tmp2, *tmp3, *tmp4, *old_tmp4, *subs[ 11 ], *ptr;
   struct atom *atom, **r;
   struct stack *results;
   int escape, repeat, count, len2, len3, change_case, first, begin, end, i;
   regex_t *rx;

   repeat = number( stack_pop( stack ));

   atom = stack_pop( stack );
   the_string = atom->syntax;
   len3 = length( atom->flags );

   atom = stack_pop( stack );
   replacement = atom->syntax;
   len2 = length( atom->flags );

   rx = ( *stack->top )->data.regexp;

   tmp2 = replacement;
   tmp3 = the_string;
   tmp4 = tmp3;

   change_case = 0;

   count = 0;
   old_tmp4 = NULL;

   first = 1;
   string_clear( private_string );

   for( ; ; )
   {
      if ( !first && ( tmp4 - tmp3 ) >= len3 )
         break;

      first = 0;

      if ( tmp4 == old_tmp4 )
         string_append( private_string, *tmp4++ );

      old_tmp4 = tmp4;

      results = apply_regexp( rx, tmp4, len3 - ( tmp4 - tmp3 ), 1 );

      if ( results == NULL )
         break;

      begin = number( results->values[ 0 ] );
      end = number( results->values[ 1 ] );
      i = 0;

      for( r= &results->values[ 2 ] ; r <= results->top; ++r )
      {
         subs[ i++ ] = ( *r )->syntax;

         if ( i > 10 )
            break;
      }

      stack_free( results );

      ptr = tmp4;

      if ( begin )
         for( i = 0; i < begin; ++i )
            string_append( private_string, *ptr++ );

      escape = 0;

      for( ptr = tmp2; *ptr; ++ptr )
      {
         char c[ 2 ];

         if ( *ptr == '\\' )
         {
            if ( escape )
               string_append( private_string, '\\' );

            escape ^= 1;
            continue;
         }
         else if ( escape )
         {
            c[ 0 ] = *ptr;
            c[ 1 ] = '\0';

            escape = 0;

            if ( *ptr >= '1' && *ptr <= '9' )
            {
               tmp1 = subs[ atoi( c ) ];
               while( *tmp1 )
               {
                  change_case = add_char( tmp1, change_case );
                  ++tmp1;
               }
               continue;
            }

            switch( *ptr )
            {
               case '0':
                  tmp1 = subs[ 10 ];
                  while( *tmp1 )
                  {
                     change_case = add_char( tmp1, change_case );
                     ++tmp1;
                  }
                  continue;

               case '&':
                  tmp1 = subs[ 0 ];
                  while( *tmp1 )
                  {
                     change_case = add_char( tmp1, change_case );
                     ++tmp1;
                  }
                  continue;

               case 't':
                  string_append( private_string, '\t' );
                  continue;

               case 'b':
                  string_append( private_string, ' ' );
                  continue;

               case 'U':
                  change_case = 2;
                  continue;

               case 'u':
                  change_case = 1;
                  continue;

               case 'L':
                  change_case = 4;
                  continue;

               case 'l':
                  change_case = 3;
                  continue;

               case 'e':
                  change_case = 0;
                  continue;

               default:
                  string_append( private_string, '\\' );
                  continue;
            }
         }

         change_case = add_char( ptr, change_case );
      }

      tmp4 = &tmp4[ end ];

      if ( ++count == repeat )
         break;
   }

   for( ptr = tmp4; *ptr; ++ptr )
      change_case = add_char( ptr, change_case );

   *stack->top = make_atom_from_string( private_string->str, private_string->used, 1 );

   return;
}

void redirect()
{
   int flags, arg1, arg3, fd;
   struct atom *arg2;
   mode_t mode;

   arg3 = number( stack_pop( stack ));
   arg2 = stack_pop( stack );
   arg1 = number( *stack->top );

   flags = 0;

   if( arg1 )
   {
      fflush( ( arg1 == 1 ? stdout : stderr ));
      flags = ( arg3 ? O_APPEND : O_CREAT );
   }

   flags |= ( arg1 == 0 ? O_RDONLY | O_SHLOCK | O_NONBLOCK :
                          O_WRONLY | O_EXLOCK | O_NONBLOCK );

   mode = getmode( setmode( "0600" ), 0 );

AGAIN:
   if (( fd = open( arg2->syntax, flags, mode )) < 0 )
   {
      if ( errno == EINTR )
         goto AGAIN;

      if ( errno == ENOENT )
      {
         *stack->top = toptr( -1 );
         return;
      }
      else if ( errno == EACCES )
      {
         *stack->top = toptr( -2 );
         return;
      }
      else if ( errno == EBUSY || errno == EAGAIN )
      {
         *stack->top = toptr( -3 );
         return;
      }

      *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
      return;
   }

   if ( arg1 && arg3 == 0 )
      if ( ftruncate( fd, 0 ))
      {
         close( fd );

         if ( errno == EBUSY )
         {
            *stack->top = toptr( -3 );
            return;
         }

         *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
         return;
      }

   if (( flags = dup( arg1 )) < 0 )
   {
      close( fd );
      fprintf( stderr, "redirect: dup: %s.\n", strerror( errno ));
      exit( 1 );
   }

   switch( arg1 )
   {
      case 0:
         fclose( stdin );
         break;

      case 1:
         fclose( stdout );
         break;

      case 2:
         fclose( stderr );
         break;
   }

   stack_push( descriptors[ arg1 ], ( struct atom *)flags );

   if ( dup2( fd, arg1 ) < 0 )
   {
      close( fd );
      resume_descriptor( arg1 );
      *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
      return;
   }

   close( fd );

   {
      FILE *fp = NULL;

      switch( arg1 )
      {
         case 0:
            stdin = fp = fdopen( arg1, "r" );
            break;

         case 1:
            stdout = fp = fdopen( arg1, ( arg3 ? "a" : "w" ));
            break;

         case 2:
            stderr = fp = fdopen( arg1, ( arg3 ? "a" : "w" ));
            break;
      }

      if ( fp == NULL )
      {
         resume_descriptor( arg1 );
         *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
         return;
      }
   }

   *stack->top = toptr( 1 );
}

void pipe_open( char *syntax, char *task, int wrt )
{
   int fd[ 2 ], pid, flag;
   FILE *fp = NULL;
   char *args[ 4 ];

   if ( pipe( &fd[ 0 ] ) < 0 )
   {
      *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
      exit( 1 );
   }

   switch(( pid = fork() ))
   {
      case -1:
         close( fd[ 0 ] );
         close( fd[ 1 ] );
         *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
         exit( 1 );

      case 0:
         if (( dup2( fd[ 1 ], !wrt )) < 0 )
         {
            fprintf( stderr, "%s (child): dup2: %s.\n", syntax, strerror( errno ));
            _exit( 1 );
         }

         close( fd[ 0 ] );
         close( fd[ 1 ] );

         args[ 0 ] = "/bin/sh";
         args[ 1 ] = "-c";
         args[ 2 ] = task;
         args[ 3 ] = NULL;

         execv( args[ 0 ], args );
         fprintf( stderr, "%s: (child): execv: %s.\n", syntax, strerror( errno ));
         _exit( 1 );

      default:
         close( fd[ 1 ] );

         if (( flag = dup( wrt )) < 0 )
         {
            close( fd[ 0 ] );
            *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
            return;
         }

         stack_push( descriptors[ wrt ], ( void *)flag );

         switch( wrt )
         {
            case 0:
               fclose( stdin );
               stdin = NULL;
               break;

            case 1:
               fclose( stdout );
               stdout = NULL;
               break;

            case 2:
               fclose( stderr );
               stderr = NULL;
               break;
         }

         if ( dup2( fd[ 0 ], wrt ) < 0 )
         {
            close( fd[ 0 ] );
            resume_descriptor( wrt );
            *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
            return;
         }

         close( fd[ 0 ] );

         switch( wrt )
         {
            case 0:
               stdin = fp = fdopen( wrt, "r" );
               break;

            case 1:
               stdout = fp = fdopen( wrt, "w" );
               break;

            case 2:
               stderr = fp = fdopen( wrt, "w" );
               break;
          }

         if ( fp == NULL )
         {
            resume_descriptor( wrt );
            *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
            return;
         }

   }

   *stack->top = toptr( 1 );
}

void directory()
{
   DIR *dir;
   struct dirent *dp;
   struct stack *stk;

   if (( dir = opendir( ( *stack->top )->syntax )) == NULL )
   {
      *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
      return;
   }

   readdir( dir );

   stk = make_stack();

   while(( dp = readdir( dir )) != NULL )
      stack_push( stk, make_atom_from_string( dp->d_name, dp->d_namlen, 1 ));

   *stack->top = make_atom_from_stack( stk );

   closedir( dir );
}

void file_rename()
{
   struct atom *to;

   to = stack_pop( stack );

   if ( rename( ( *stack->top )->syntax, to->syntax )  < 0 )
      *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
   else
      *stack->top = toptr( 0 );
}

void file_remove()
{
   if ( remove( ( *stack->top )->syntax ) < 0 )
      *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
   else
      *stack->top = toptr( 0 );
}

void file_stat()
{
   struct stat st;
   struct group *gp;
   struct passwd *pw;
   struct stack *result;
   char buffer[ 17 ];

   if ( stat( ( *stack->top )->syntax, &st ) < 0 )
   {
      *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
      return;
   }

   result = make_stack();
   *stack->top = make_atom_from_stack( result );

   pw = getpwuid( st.st_uid );

   if ( pw == NULL )
      stack_push( result, toptr( st.st_uid ) );
   else
      stack_push( result, make_atom_from_string( pw->pw_name, -1, 1 ));

   gp = getgrgid( st.st_gid );

   if ( gp == NULL )
      stack_push( result, toptr( st.st_gid ));
   else
      stack_push( result, make_atom_from_string( gp->gr_name, -1, 1 ));

   snprintf( buffer, sizeof( buffer ), "%016lu", st.st_atime );
   stack_push( result, make_atom_from_string( buffer, -1, 1 ));

   snprintf( buffer, sizeof( buffer ), "%016lu", st.st_mtime );
   stack_push( result, make_atom_from_string( buffer, -1, 1 ));

   snprintf( buffer, sizeof( buffer ), "%016lu", st.st_size );
   stack_push( result, make_atom_from_string( buffer, -1, 1 ));
}

void file_symlink()
{
   struct atom *link;

   link = stack_pop( stack );

   if ( symlink( ( *stack->top )->syntax,
                 link->syntax ) < 0 )
      *stack->top = make_atom_from_string( strerror( errno ), -1, 1 );
   else
      *stack->top = toptr( 0 );
}

void expand_tabs()
{
   struct atom *arg2;
   int len, i, offset, tabstop;
   char *ptr;

   arg2 = stack_pop( stack );
   ptr = arg2->syntax;
   len = length( arg2->flags );

   tabstop = number( *stack->top );
   offset = 0;

   string_clear( working_string );

   for( i = 0; i < len; ++i )
   {
      if ( *ptr == '\t' )
      {
         int spaces;

         spaces = tabstop - ( i + offset ) % tabstop;
         offset += spaces - 1;

         while( spaces-- )
            string_append( working_string, ' ' );
      }
      else
         string_append( working_string, *ptr );

      ++ptr;
   }

   *stack->top = make_atom_from_string( working_string->str, working_string->used, 1 );
}

void substack()
{
   struct atom *stk;
   struct stack *new;
   int from, to, i;

   to = number( stack_pop( stack ) );
   from = number( stack_pop( stack ));
   stk = stack_pop( stack );

   new = make_stack();

   for( i = from; i <= to; ++i )
      stack_push( new, stk->data.stack->values[ i ] );

   stack_push( stack, make_atom_from_stack( new ));
}

void append_stacks()
{
   int total, i, j;
   struct stack *new;
   struct atom *stk;

   total = number( stack_pop( stack ));
   new = make_stack();

   for( j = stack->used - total; j < stack->used; ++j )
   {
      stk = stack->values[ j ];

      for( i = 0; i < stk->data.stack->used; ++i )
         stack_push( new, stk->data.stack->values[ i ] );
   }

   while( total-- )
      stack_pop( stack );

   stack_push( stack, make_atom_from_stack( new ));
}


syntax highlighted by Code2HTML, v. 0.9.1