state = $state; } } abstract class BoltUnit { public $name; // logical name. abstract function walk($old_stack, &$new_stack, $next, &$item); // multi-purpose function for traversing a course. // if $old_stack is null // set up initial state for this unit. // append frames to $new_stack for this unit and descendants // $next is ignored // $item is the initial item // return is ignored // else // The first frame of $old_stack is for this unit. // Check for name mismatch (if changed course). // Append frames to $new_stack for this unit and descendants // if $next, the bottom-level non-item unit should increment. // return value: true if the caller should increment abstract function is_item(); } // An iterator represents a user's position in a course. // It is stored in the database, and the course may change underneath it. // class BoltIter { public $stack; // array of BoltFrame public $top; // point to the start of a course; set up stack. // function __construct($top) { $this->top = $top; $this->stack = null; } // get current item // function at() { $new_stack = array(); $this->top->walk($this->stack, $new_stack, false, $item); $this->stack = $new_stack; return $item; } // move to the next item (and return it) // return true if we're off the end // function next() { $item = null; $new_stack = array(); $this->top->walk($this->stack, $new_stack, true, $item); $this->stack = $new_stack; return $item; } } class BoltSeq extends BoltUnit { public $units; function __construct($n, $u) { $this->name = $n; $this->units = $u; } function walk($old_stack, &$new_stack, $next, &$item) { //echo "call to walk() for $this->name: next: $next\n"; if ($old_stack) { //echo "old stack: \n"; //var_dump($old_stack); //echo "------------\n"; $frame = $old_stack[0]; $state = $frame->state; $i = $state->i; $restart = false; if ($i >= count($this->units)) { echo "index too large - restarting\n"; $restart = true; } $child = $this->units[$i]; if ($state->child_name != $child->name) { echo "bad name - restarting\n"; $restart = true; } if (!$restart) { if ($next) { $inc = false; if ($child->is_item()) { $inc = true; } else { array_shift($old_stack); $inc = $child->walk($old_stack, $new_stack, true, &$item); } if ($inc) { $i++; if ($i == count($this->units)) { return true; } } } } } else { $restart = true; } if ($restart) { $i = 0; } $child = $this->units[$i]; $state->i = $i; $state->child_name = $child->name; $frame = new BoltFrame($state); $new_stack[] = $frame; if ($child->is_item()) { $item = $child; } else { $child->walk(null, $new_stack, false, $item); } } function is_item() { return false; } } class BoltItem extends BoltUnit { public $filename; function __construct($name, $filename) { $this->filename = $filename; $this->name = $name; } function begin() { return array(new BoltFrame($this)); } function unit_list() { return array(&$this); } function is_item() { return true; } function walk($old_stack, &$new_stack, $next, &$item) { echo "SHOULDN'T BE HERE\n"; } } class BoltLesson extends BoltItem { } class BoltExercise extends BoltItem { } function enum_course($course) { $iter = new BoltIter($course); while (1) { $x = $iter->at(); if (!$x) break; echo "at: $x->url\n"; $x = $iter->next(); if (!$x) break; echo "next: $x->filename\n"; } echo "course over\n"; } ?>