diff --git a/src/builtin.cmod b/src/builtin.cmod
index 07d8e93f199a54a2baa8d9463b2883bfee4125c8..f1d42d291b98657484a84eeda4735bd1b252c5fb 100644
--- a/src/builtin.cmod
+++ b/src/builtin.cmod
@@ -2,7 +2,7 @@
 || This file is part of Pike. For copyright information see COPYRIGHT.
 || Pike is distributed under GPL, LGPL and MPL. See the file COPYING
 || for more information.
-|| $Id: builtin.cmod,v 1.161 2004/09/02 14:34:59 grubba Exp $
+|| $Id: builtin.cmod,v 1.162 2004/09/10 15:24:42 grubba Exp $
 */
 
 #include "global.h"
@@ -31,6 +31,7 @@
 #include "fsort.h"
 #include "port.h"
 #include "gc.h"
+#include "block_alloc.h"
 #include <assert.h>
 
 /*! @decl array(array(int|string)) describe_program(program p)
@@ -2913,6 +2914,453 @@ PIKEFUN array __automap__(mixed fun, mixed ... tmpargs)
   stack_unlink(args);
 }
 
+/* Linked list stuff.
+ */
+#undef INIT_BLOCK
+#define INIT_BLOCK(NODE) do {				\
+    (NODE)->next = (NODE)->prev = NULL;			\
+    (NODE)->refs = 1;					\
+    (NODE)->val.type = T_INT;				\
+    (NODE)->val.subtype = NUMBER_UNDEFINED;		\
+    (NODE)->val.u.integer = 0;				\
+  } while(0) 
+
+#undef EXIT_BLOCK
+#define EXIT_BLOCK(NODE) do {				\
+    free_svalue(&(NODE)->val);				\
+  } while(0)
+
+BLOCK_ALLOC_FILL_PAGES(list_node, 4);
+
+PMOD_EXPORT void free_list_node(struct list_node *node)
+{
+  if (!sub_ref(node)) {
+    really_free_list_node(node);
+  }
+}
+
+PMOD_EXPORT void unlink_list_node(struct list_node *n)
+{
+#ifdef PIKE_DEBUG
+  if (!n) {
+    Pike_fatal("Unlinking NULL node.\n");
+  }
+  if (!n->next || !n->prev) {
+    Pike_fatal("Unlinking unlinked node.\n");
+  }
+#endif /* PIKE_DEBUG */
+  n->prev->next = n->next;
+  n->next->prev = n->prev;
+  n->next = n->prev = NULL;
+
+  /* We've lost two references. */
+  free_list_node(n);
+  free_list_node(n);
+}
+
+PMOD_EXPORT void prepend_list_node(struct list_node *node,
+				   struct list_node *new)
+{
+#ifdef PIKE_DEBUG
+  if (!node) {
+    Pike_fatal("No node to prepend.\n");
+  }
+  if (!node->prev) {
+    Pike_fatal("Prepending unhooked node.\n");
+  }
+  if (!new) {
+    Pike_fatal("Prepending NULL node.\n");
+  }
+  if (new->next || new->prev) {
+    Pike_fatal("Prepending hooked node.\n");
+  }
+#endif /* PIKE_DEBUG */
+  new->next = node;
+  new->prev = node->prev;
+  new->prev->next = node->prev = new;
+  add_ref(new);
+  add_ref(new);
+}
+
+PMOD_EXPORT void append_list_node(struct list_node *node,
+				  struct list_node *new)
+{
+#ifdef PIKE_DEBUG
+  if (!node) {
+    Pike_fatal("No node to append.\n");
+  }
+  if (!node->next) {
+    Pike_fatal("Appending unhooked node.\n");
+  }
+  if (!new) {
+    Pike_fatal("Appending NULL node.\n");
+  }
+  if (new->next || new->prev) {
+    Pike_fatal("Appending hooked node.\n");
+  }
+#endif /* PIKE_DEBUG */
+  new->next = node->next;
+  new->prev = node;
+  new->next->prev = node->next = new;
+  add_ref(new);
+  add_ref(new);
+}
+
+/*! @class List
+ *!
+ *!   Linked list of values.
+ */
+PIKECLASS List
+{
+  CVAR struct list_node *head;		/* Doubles as head sentinel->next. */
+  CVAR INT32 head_sentinel_refs;
+  CVAR struct list_node *tail;		/* NULL. head s->prev & tail s->next */
+  CVAR INT32 tail_sentinel_refs;
+  CVAR struct list_node *tail_pred;	/* Doubles as tail sentinel->prev. */
+
+#define HEAD_SENTINEL(this)	((struct list_node *)(&this->head))
+#define TAIL_SENTINEL(this)	((struct list_node *)(&this->tail))
+
+  INIT
+  {
+    THIS->tail = NULL;
+    THIS->head = TAIL_SENTINEL(THIS);
+    THIS->tail_pred = HEAD_SENTINEL(THIS);
+    THIS->head_sentinel_refs = THIS->tail_sentinel_refs = 1;
+  }
+
+  EXIT
+  {
+    struct list_node *node = THIS->head;
+    struct list_node *next;
+    while ((next = node->next)) {
+      unlink_list_node(node);
+      node = next;
+    }
+  }
+
+  /*! @decl void append(mixed ... values)
+   *!
+   *!   Append @[values] to the end of the list.
+   *!
+   *! @seealso
+   *!   @[insert()]
+   */
+  PIKEFUN void append(mixed ... values)
+  {
+    struct list_node *node = TAIL_SENTINEL(THIS);
+    while (args--) {
+      struct list_node *new = alloc_list_node();
+      new->val = *(--Pike_sp);
+      prepend_list_node(node, new);
+      free_list_node(node = new);
+    }
+    push_int(0);
+  }
+
+  /*! @decl void insert(mixed ... values)
+   *!
+   *!   Insert @[values] at the front of the list.
+   *!
+   *! @seealso
+   *!   @[append()]
+   */
+  PIKEFUN void insert(mixed ... values)
+  {
+    struct list_node *node = THIS->head;
+    while (args--) {
+      struct list_node *new = alloc_list_node();
+      new->val = *(--Pike_sp);
+      prepend_list_node(node, new);
+      free_list_node(node = new);
+    }
+    push_int(0);
+  }
+
+  /*! @decl void create(mixed ... values)
+   *!
+   *!   Create a new @[List], and initialize it with @[values].
+   *!
+   *! @fixme
+   *!   Ought to reset the @[List] if called multiple times.
+   */
+  PIKEFUN void create(mixed ... values)
+    flags ID_STATIC;
+  {
+    /* FIXME: Reset the list? */
+    apply_current(f_List_append_fun_num, args);
+  }
+
+  /*! @class _get_iterator
+   *!
+   *!   @[Iterator] that loops over the @[List].
+   */
+  PIKECLASS _get_iterator
+    program_flags PROGRAM_USES_PARENT;
+    flags ID_STATIC;
+  {
+    CVAR struct list_node *cur;
+    CVAR INT32 ind;
+
+    INIT
+    {
+      struct external_variable_context loc;
+      struct List_struct *parent;
+
+      /* Find our parent. */
+      loc.o = Pike_fp->current_object;
+      loc.parent_identifier = Pike_fp->fun;
+      loc.inherit = INHERIT_FROM_INT(loc.o->prog, loc.parent_identifier);
+      find_external_context(&loc, 1);
+      parent = (struct List_struct *)(loc.o->storage +
+				      loc.inherit->storage_offset);
+      add_ref(THIS->cur = parent->head);
+      THIS->ind = 0;
+    }
+
+    EXIT
+    {
+      if (THIS->cur) {
+	free_list_node(THIS->cur);
+	THIS->cur = NULL;
+      }
+    }
+
+    PIKEFUN int(0..1) `!()
+      flags ID_STATIC;
+    {
+      pop_n_elems(args);
+      push_int(!THIS->cur->next || !THIS->cur->prev);
+    }
+
+    PIKEFUN int(0..) index()
+    {
+      pop_n_elems(args);
+      if (THIS->cur->next && THIS->cur->prev) {
+	push_int(THIS->ind);
+      } else {
+	push_undefined();
+      }
+    }
+
+    /*! @decl mixed value()
+     *!
+     *! @returns
+     *!   Returns the value at the current position.
+     */
+    PIKEFUN mixed value()
+    {
+      pop_n_elems(args);
+      if (THIS->cur->next && THIS->cur->prev) {
+	push_svalue(&THIS->cur->val);
+      } else {
+	push_undefined();
+      }
+    }
+
+    /*! @decl int(0..1) first()
+     *!
+     *!   Reset the iterator.
+     *!
+     *! @returns
+     *!   Returns @expr{1@} if there are elements in the list,
+     *!   and @expr{0@} (zero) if the list is empty.
+     */
+    PIKEFUN int(0..1) first()
+    {
+      struct external_variable_context loc;
+      struct List_struct *parent;
+      pop_n_elems(args);
+
+      /* Find our parent. */
+      loc.o = Pike_fp->current_object;
+      loc.parent_identifier = Pike_fp->fun;
+      loc.inherit = INHERIT_FROM_INT(loc.o->prog, loc.parent_identifier);
+      find_external_context(&loc, 1);
+      parent = (struct List_struct *)(loc.o->storage +
+				      loc.inherit->storage_offset);
+      free_list_node(THIS->cur);
+      add_ref(THIS->cur = parent->head);
+      THIS->ind = 0;
+      pop_n_elems(args);
+      if (THIS->cur->next) {
+	push_int(1);
+      } else {
+	push_undefined();
+      }
+    }
+
+    /*! @decl int(0..1) next()
+     *!
+     *!   Advance to the next element in the list.
+     *!
+     *! @returns
+     *!   Returns @expr{1@} on success, and @expr{0@} (zero)
+     *!   at the end of the list.
+     *!
+     *! @seealso
+     *!   @[prev()]
+     */
+    PIKEFUN int(0..1) next()
+    {
+      struct list_node *next;
+      if ((next = THIS->cur->next)) {
+	free_list_node(THIS->cur);
+	add_ref(THIS->cur = next);
+	THIS->ind++;
+	if (next->next) {
+	  pop_n_elems(args);
+	  push_int(1);
+	  return;
+	}
+      }
+      pop_n_elems(args);
+      push_int(0);
+    }
+
+    /*! @decl int(0..1) prev()
+     *!
+     *!   Retrace to the previous element in the list.
+     *!
+     *! @returns
+     *!   Returns @expr{1@} on success, and @expr{0@} (zero)
+     *!   at the beginning of the list.
+     *!
+     *! @seealso
+     *!   @[next()]
+     */
+    PIKEFUN int(0..1) prev()
+    {
+      struct list_node *prev;
+      if ((prev = THIS->cur->prev)) {
+	free_list_node(THIS->cur);
+	add_ref(THIS->cur = prev);
+	THIS->ind--;
+	if (prev->prev) {
+	  pop_n_elems(args);
+	  push_int(1);
+	  return;
+	}
+      }
+      pop_n_elems(args);
+      push_int(0);
+    }
+
+    /*! @decl Iterator `+=(int steps)
+     *!
+     *!   Advance or retrace the specified number of @[steps].
+     *!
+     *! @seealso
+     *!   @[next()], @[prev]
+     */
+    PIKEFUN Iterator `+=(int steps)
+    {
+      if (!steps) return;
+      if (steps > 0) {
+	while (steps--) {
+	  apply_current(f_List_cq__get_iterator_next_fun_num, 0);
+	  pop_stack();
+	}
+      } else {
+	while (steps++) {
+	  apply_current(f_List_cq__get_iterator_prev_fun_num, 0);
+	  pop_stack();
+	}
+      }
+      pop_n_elems(args);
+      ref_push_object(Pike_fp->current_object);
+    }
+
+    /*! @decl void insert(mixed val)
+     *!
+     *!   Insert @[val] at the current position.
+     *!
+     *! @seealso
+     *!   @[append()], @[delete()], @[set()]
+     */
+    PIKEFUN void insert(mixed val)
+    {
+      struct list_node *new;
+      if (!THIS->cur->prev) {
+	Pike_error("Attempt to insert before the start sentinel.\n");
+      }
+      new = alloc_list_node();
+      assign_svalue_no_free(&new->val, val);
+      prepend_list_node(THIS->cur, new);
+      free_list_node(THIS->cur);
+      THIS->cur = new;
+      pop_n_elems(args);
+      push_int(0);
+    }
+
+    /*! @decl void append(mixed val)
+     *!
+     *!   Append @[val] after the current position.
+     *!
+     *! @seealso
+     *!   @[insert()], @[delete()], @[set()]
+     */
+    PIKEFUN void append(mixed val)
+    {
+      struct list_node *new;
+      if (!THIS->cur->next) {
+	Pike_error("Attempt to append after the end sentinel.\n");
+      }
+      new = alloc_list_node();
+      assign_svalue_no_free(&new->val, val);
+      append_list_node(THIS->cur, new);
+      free_list_node(new);
+      pop_n_elems(args);
+      push_int(0);
+    }
+
+    /*! @decl void delete()
+     *!
+     *!   Delete the current node.
+     *!
+     *!   The current position will advance to the next node.
+     *!   This function thus performes the reverse operation
+     *!   of @[insert()].
+     *!
+     *! @seealso
+     *!   @[insert()], @[append()], @[set()]
+     */
+    PIKEFUN void delete()
+    {
+      struct list_node *next;
+      if (!(next = THIS->cur->next) || !THIS->cur->prev) {
+	Pike_error("Attempt to delete a sentinel.\n");
+      }
+      unlink_list_node(THIS->cur);
+      free_list_node(THIS->cur);
+      add_ref(THIS->cur = next);
+      pop_n_elems(args);
+      push_int(0);
+    }
+
+    /*! @decl void set(mixed val)
+     *!
+     *!   Set the value of the current position to @[val].
+     *!
+     *! @seealso
+     *!   @[insert()], @[append()], @[delete()]
+     */
+    PIKEFUN void set(mixed val)
+    {
+      if (!THIS->cur->next || !THIS->cur->prev) {
+	Pike_error("Attempt to set a sentinel.\n");
+      }
+      assign_svalue(&THIS->cur->val, val);
+      pop_n_elems(args);
+      push_int(0);
+    }
+  }
+  /*! @endclass
+   */
+}
+/*! @endclass
+ */
+
 void init_builtin(void)
 {
 INIT
diff --git a/src/builtin_functions.h b/src/builtin_functions.h
index a445fc1abf68e6fac270b0dbccb6d31411a40c1c..a094ca1e8992326f271a4140e255691d0b3064b6 100644
--- a/src/builtin_functions.h
+++ b/src/builtin_functions.h
@@ -2,7 +2,7 @@
 || This file is part of Pike. For copyright information see COPYRIGHT.
 || Pike is distributed under GPL, LGPL and MPL. See the file COPYING
 || for more information.
-|| $Id: builtin_functions.h,v 1.30 2004/03/07 02:19:09 nilsson Exp $
+|| $Id: builtin_functions.h,v 1.31 2004/09/10 15:24:42 grubba Exp $
 */
 
 #ifndef BUILTIN_EFUNS_H
@@ -11,6 +11,7 @@
 #define TYPEP(ID,NAME,TYPE) PMOD_EXPORT void ID(INT32 args);
 
 #include "callback.h"
+#include "block_alloc_h.h"
 
 /* Weak flags for arrays, multisets and mappings. 1 is avoided for
  * compatibility reasons. */
@@ -158,6 +159,22 @@ void f_function_object(INT32 args);
 void f_function_program(INT32 args);
 void f_random(INT32 args);
 PMOD_EXPORT void f_backtrace(INT32 args);
+
+struct list_node
+{
+  /* NOTE: Unusual order of elements due to use of sentinels. */
+  struct list_node *next;
+  INT32 refs;
+  struct list_node *prev;
+  struct svalue val;
+};
+BLOCK_ALLOC_FILL_PAGES(list_node, 4);
+PMOD_EXPORT void free_list_node(struct list_node *node);
+PMOD_EXPORT void unlink_list_node(struct list_node *n);
+PMOD_EXPORT void prepend_list_node(struct list_node *node,
+				   struct list_node *new);
+PMOD_EXPORT void append_list_node(struct list_node *node,
+				  struct list_node *new);
 void init_builtin(void);
 void exit_builtin(void);