Add Bocfel interpreter
[projects/chimara/chimara.git] / interpreters / bocfel / objects.c
diff --git a/interpreters/bocfel/objects.c b/interpreters/bocfel/objects.c
new file mode 100644 (file)
index 0000000..ae68839
--- /dev/null
@@ -0,0 +1,436 @@
+/*-
+ * Copyright 2009-2012 Chris Spiegel.
+ *
+ * This file is part of Bocfel.
+ *
+ * Bocfel is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version
+ * 2 or 3, as published by the Free Software Foundation.
+ *
+ * Bocfel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Bocfel.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "objects.h"
+#include "branch.h"
+#include "memory.h"
+#include "process.h"
+#include "screen.h"
+#include "util.h"
+#include "zterp.h"
+
+static uint16_t OBJECT(uint16_t n)
+{
+  /* Use 32-bit arithmetic to detect 16-bit overflow. */
+  uint32_t base = header.objects, obj = n, addr;
+  int objsize;
+
+  if(zversion <= 3)
+  {
+    ZASSERT(n <= 255, "illegal object %u referenced", (unsigned)n);
+    addr = base + (31 * 2) + (9 * (obj - 1));
+    objsize = 9;
+  }
+  else
+  {
+    addr = base + (63 * 2) + (14 * (obj - 1));
+    objsize = 14;
+  }
+
+  ZASSERT(addr + objsize < header.static_start, "object %u out of range", (unsigned)n);
+
+  return addr;
+}
+
+#define OFFSET_PARENT  (zversion <= 3 ? 4 :  6)
+#define OFFSET_SIBLING (zversion <= 3 ? 5 :  8)
+#define OFFSET_CHILD   (zversion <= 3 ? 6 : 10)
+#define OFFSET_PROP    (zversion <= 3 ? 7 : 12)
+
+#define PARENT(object)         RELATION(object, OFFSET_PARENT)
+#define SIBLING(object)                RELATION(object, OFFSET_SIBLING)
+#define CHILD(object)          RELATION(object, OFFSET_CHILD)
+
+#define SET_PARENT(obj1, obj2) SET_OBJECT(obj1, obj2, OFFSET_PARENT)
+#define SET_SIBLING(obj1, obj2)        SET_OBJECT(obj1, obj2, OFFSET_SIBLING)
+#define SET_CHILD(obj1, obj2)  SET_OBJECT(obj1, obj2, OFFSET_CHILD)
+
+static uint16_t PROPADDR(uint16_t n)
+{
+  return WORD(OBJECT(n) + OFFSET_PROP);
+}
+
+static uint16_t RELATION(uint16_t object, int offset)
+{
+  return zversion <= 3 ? BYTE(OBJECT(object) + offset) : WORD(OBJECT(object) + offset);
+}
+
+/*
+ * the 32 attribute flags     parent     sibling     child   properties
+ * ---32 bits in 4 bytes---   ---3 bytes------------------  ---2 bytes--
+ *
+ * the 48 attribute flags     parent    sibling   child     properties
+ * ---48 bits in 6 bytes---   ---3 words, i.e. 6 bytes----  ---2 bytes--
+ */
+static void SET_OBJECT(uint16_t obj1, uint16_t obj2, int offset)
+{
+  if(zversion <= 3) STORE_BYTE(OBJECT(obj1) + offset, obj2);
+  else              STORE_WORD(OBJECT(obj1) + offset, obj2);
+}
+
+static void remove_obj(uint16_t object)
+{
+  uint16_t parent = PARENT(object);
+
+  if(parent != 0)
+  {
+    uint16_t child = CHILD(parent);
+
+    /* Direct child */
+    if(child == object)
+    {
+      /* parent->child = parent->child->sibling */
+      SET_CHILD(parent, SIBLING(child));
+    }
+    else
+    {
+      while(SIBLING(child) != object)
+      {
+        /* child = child->sibling */
+        child = SIBLING(child);
+      }
+
+      /* Now the sibling of child is the object to remove. */
+
+      /* child->sibling = child->sibling->sibling */
+      SET_SIBLING(child, SIBLING(SIBLING(child)));
+    }
+
+    /* object->parent = 0 */
+    SET_PARENT(object, 0);
+
+    /* object->sibling = 0 */
+    SET_SIBLING(object, 0);
+  }
+}
+
+static uint16_t property_length(uint16_t propaddr)
+{
+  uint16_t length;
+  /* The address is to the data; the size byte is right before. */
+  uint8_t byte = user_byte(propaddr - 1);
+
+  if(zversion <= 3)
+  {
+    length = (byte >> 5) + 1;
+  }
+  else
+  {
+    if(byte & 0x80)
+    {
+      length = byte & 0x3f;
+      if(length == 0) length = 64;
+    }
+    else
+    {
+      length = (byte & 0x40) ? 2 : 1;
+    }
+  }
+
+  return length;
+}
+
+static uint8_t PROPERTY(uint16_t addr)
+{
+  uint8_t propnum;
+
+  if(zversion <= 3)
+  {
+    propnum = user_byte(addr - 1) & 0x1f;
+  }
+  else
+  {
+    if(user_byte(addr - 1) & 0x80) propnum = user_byte(addr - 2) & 0x3f;
+    else                           propnum = user_byte(addr - 1) & 0x3f;
+  }
+
+  return propnum;
+}
+
+static uint16_t advance_prop_addr(uint16_t propaddr)
+{
+  uint8_t size;
+
+  size = user_byte(propaddr++);
+
+  if(size == 0) return 0;
+
+  if(zversion >= 4 && (size & 0x80)) propaddr++;
+
+  return propaddr;
+}
+
+static uint16_t first_property(uint16_t object)
+{
+  uint16_t propaddr = PROPADDR(object);
+
+  propaddr += (2 * user_byte(propaddr)) + 1;
+
+  return advance_prop_addr(propaddr);
+}
+
+static uint16_t next_property(uint16_t propaddr)
+{
+  propaddr += property_length(propaddr);
+
+  return advance_prop_addr(propaddr);
+}
+
+#define FOR_EACH_PROPERTY(object, addr) for(uint16_t addr = first_property(object); addr != 0; addr = next_property(addr))
+
+static int find_prop(uint16_t object, uint16_t property, uint16_t *propaddr, uint16_t *length)
+{
+  FOR_EACH_PROPERTY(object, addr)
+  {
+    if(PROPERTY(addr) == property)
+    {
+      *propaddr = addr;
+      *length = property_length(addr);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static void check_attr(uint16_t attr)
+{
+  ZASSERT(attr <= (zversion <= 3 ? 31 : 47), "invalid attribute: %u", (unsigned)attr);
+}
+
+static int is_zero(int is_store, int is_jump)
+{
+  if(zargs[0] == 0)
+  {
+    if(is_store) store(0);
+    if(is_jump)  branch_if(0);
+
+    return 1;
+  }
+
+  return 0;
+}
+
+#define check_zero(store, jump)        do { if(is_zero(store, jump)) return; } while(0)
+
+/* Attributes are stored at the very beginning of an object, so the
+ * address OBJECT() returns refers directly to the attributes.  The
+ * leftmost bit is attribute 0.  Thus these attribute functions need to
+ * find out first which byte of the attributes to look at; this is done
+ * by dividing by 8.  Attributes 0-7 will be in byte 0, 8-15 in byte 1,
+ * and so on.  Then the particular bit is found.  Attributes 0..7 are
+ * bits 7..0, attributes 8..15 are 7..0, and so on.  Taking the
+ * remainder of the attribute divided by 8 gives the bit position,
+ * counting from the left, of the attribute.
+ */
+#define ATTR_BIT(num)          (0x80U >> ((num) % 8))
+void ztest_attr(void)
+{
+  check_zero(0, 1);
+  check_attr(zargs[1]);
+
+  uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8);
+
+  branch_if(BYTE(addr) & ATTR_BIT(zargs[1]));
+}
+
+void zset_attr(void)
+{
+  check_zero(0, 0);
+  check_attr(zargs[1]);
+
+  uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8);
+
+  STORE_BYTE(addr, BYTE(addr) | ATTR_BIT(zargs[1]));
+}
+
+void zclear_attr(void)
+{
+  check_zero(0, 0);
+  check_attr(zargs[1]);
+
+  uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8);
+
+  STORE_BYTE(addr, BYTE(addr) & ~ATTR_BIT(zargs[1]));
+}
+#undef ATTR_BIT
+
+void zremove_obj(void)
+{
+  check_zero(0, 0);
+
+  remove_obj(zargs[0]);
+}
+
+void zinsert_obj(void)
+{
+  check_zero(0, 0);
+
+  remove_obj(zargs[0]);
+
+  SET_SIBLING(zargs[0], CHILD(zargs[1]));
+  SET_CHILD(zargs[1], zargs[0]);
+  SET_PARENT(zargs[0], zargs[1]);
+}
+
+void zget_sibling(void)
+{
+  check_zero(1, 1);
+
+  uint16_t sibling = SIBLING(zargs[0]);
+
+  store(sibling);
+  branch_if(sibling != 0);
+}
+
+void zget_child(void)
+{
+  check_zero(1, 1);
+
+  uint16_t child = CHILD(zargs[0]);
+
+  store(child);
+  branch_if(child != 0);
+}
+
+void zget_parent(void)
+{
+  check_zero(1, 0);
+
+  store(PARENT(zargs[0]));
+}
+
+void zput_prop(void)
+{
+  check_zero(0, 0);
+
+  uint16_t propaddr, length;
+  int found;
+
+  found = find_prop(zargs[0], zargs[1], &propaddr, &length);
+
+  ZASSERT(found, "broken story: no prop");
+  ZASSERT(length == 1 || length == 2, "broken story: property too long: %u", (unsigned)length);
+
+  if(length == 1) user_store_byte(propaddr, zargs[2] & 0xff);
+  else            user_store_word(propaddr, zargs[2]);
+}
+
+void zget_prop(void)
+{
+  check_zero(1, 0);
+
+  uint16_t propaddr, length;
+
+  if(find_prop(zargs[0], zargs[1], &propaddr, &length))
+  {
+    if     (length == 1) store(user_byte(propaddr));
+    else if(length == 2) store(user_word(propaddr));
+
+    /* If the length is > 2, the story file is misbehaving.  At least
+     * Christminster does this, and Frotz and Nitfol allow it, reading a
+     * word, so do that here.
+     */
+    else                 store(user_word(propaddr));
+  }
+  else
+  {
+    uint32_t i;
+
+    ZASSERT(zargs[1] < (zversion <= 3 ? 32 : 64), "invalid property: %u", (unsigned)zargs[1]);
+
+    i = header.objects + (2 * (zargs[1] - 1));
+    store(WORD(i));
+  }
+}
+
+void zget_prop_len(void)
+{
+  /* Z-spec 1.1 says @get_prop_len 0 must yield 0. */
+  if(zargs[0] == 0) store(0);
+  else              store(property_length(zargs[0]));
+}
+
+void zget_prop_addr(void)
+{
+  check_zero(1, 0);
+
+  uint16_t propaddr, length;
+
+  if(find_prop(zargs[0], zargs[1], &propaddr, &length)) store(propaddr);
+  else store(0);
+}
+
+void zget_next_prop(void)
+{
+  check_zero(1, 0);
+
+  uint16_t object = zargs[0], property = zargs[1];
+  int next = 0;
+  int found_prop = 0;
+
+  FOR_EACH_PROPERTY(object, propaddr)
+  {
+    uint8_t propnum = PROPERTY(propaddr);
+
+    if(property == 0 || next)
+    {
+      found_prop = propnum;
+      break;
+    }
+
+    if(propnum == property) next = 1;
+  }
+
+  store(found_prop);
+}
+
+void zjin(void)
+{
+  /* @jin 0 0 is not defined, since @jin requires an object (§15) and
+   * object 0 is not actually an object (§12.3).  However, many
+   * interpreters yield a true value for this, and Torbjorn Andersson’s
+   * strictz tester expects it to be true, so go with the flow.
+   */
+  if(zargs[0] == 0 && zargs[1] == 0)
+  {
+    branch_if(1);
+    return;
+  }
+
+  check_zero(0, 1);
+
+  branch_if(PARENT(zargs[0]) == zargs[1]);
+}
+
+void print_object(uint16_t obj, void (*outc)(uint8_t))
+{
+  if(obj == 0) return;
+
+  print_handler(PROPADDR(obj) + 1, outc);
+}
+
+void zprint_obj(void)
+{
+  check_zero(0, 0);
+
+  print_object(zargs[0], NULL);
+}