diff src/category.c @ 17052:d0d7b244b1d0

Initial revision
author Karl Heuer <kwzh@gnu.org>
date Thu, 20 Feb 1997 07:02:49 +0000
parents
children 70194012fb3a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/category.c	Thu Feb 20 07:02:49 1997 +0000
@@ -0,0 +1,665 @@
+/* GNU Emacs routines to deal with category tables.
+   Ver.1.0
+
+   Copyright (C) 1995 Free Software Foundation, Inc.
+   Copyright (C) 1995 Electrotechnical Laboratory, JAPAN.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Emacs 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 GNU Emacs; see the file COPYING.  If not, write to
+the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+
+/* Here we handle three objects: category, category set, and category
+   table.  Read comments in the file category.h to understand them.  */
+
+#include <config.h>
+#include <ctype.h>
+#include "lisp.h"
+#include "buffer.h"
+#include "charset.h"
+#include "category.h"
+
+/* The version number of the latest category table.  Each category
+   table has a unique version number.  It is assigned a new number
+   also when it is modified.  When a regular expression is compiled
+   into the struct re_pattern_buffer, the version number of the
+   category table (of the current buffer) at that moment is also
+   embedded in the structure.
+
+   For the moment, we are not using this feature.  */
+static int category_table_version;
+
+Lisp_Object Qcategory_table, Qcategoryp, Qcategorysetp, Qcategory_table_p;
+
+/* Variables to determine word boundary.  */
+Lisp_Object Vword_combining_categories, Vword_separating_categories;
+
+/* Temporary internal variable used in macro CHAR_HAS_CATEGORY.  */
+Lisp_Object _temp_category_set;
+
+
+/* Category set staff.  */
+
+DEFUN ("make-category-set", Fmake_category_set, Smake_category_set, 1, 1, 0,
+  "Return a newly created category-set which contains CATEGORIES.\n\
+CATEGORIES is a string of category mnemonics.")
+  (categories)
+     Lisp_Object categories;
+{
+  Lisp_Object val;
+  int len;
+
+  CHECK_STRING (categories, 0);
+  val = MAKE_CATEGORY_SET;
+
+  len = XSTRING (categories)->size;
+  while (--len >= 0)
+    {
+      Lisp_Object category = make_number (XSTRING (categories)->data[len]);
+
+      CHECK_CATEGORY (category, 0);
+      SET_CATEGORY_SET (val, category, Qt);
+    }
+  return val;
+}
+
+
+/* Category staff.  */
+
+Lisp_Object check_category_table ();
+
+DEFUN ("define-category", Fdefine_category, Sdefine_category, 2, 3, 0,
+  "Define CHAR as a category which is described by DOCSTRING.\n\
+CHAR should be a visible letter of ` ' thru `~'.\n\
+DOCSTRING is a documentation string of the category.\n\
+The category is defined only in category table TABLE, which defaults to\n\
+ the current buffer's category table.")
+  (category, docstring, table)
+     Lisp_Object category, docstring, table;
+{
+  CHECK_CATEGORY (category, 0);
+  CHECK_STRING (docstring, 1);
+  table = check_category_table (table);
+
+  if (!NILP (CATEGORY_DOCSTRING (table, XFASTINT (category))))
+    error ("Category `%c' is already defined", XFASTINT (category));
+  CATEGORY_DOCSTRING (table, XFASTINT (category)) = docstring;
+
+  return Qnil;
+}
+
+DEFUN ("category-docstring", Fcategory_docstring, Scategory_docstring, 1, 2, 0,
+  "Return a documentation string of CATEGORY.\n\
+Optional second arg specifies CATEGORY-TABLE,\n\
+ which defaults to the current buffer's category table.")
+  (category, table)
+     Lisp_Object category, table;
+{
+  Lisp_Object doc;
+
+  CHECK_CATEGORY (category, 0);
+  table = check_category_table (table);
+
+  return CATEGORY_DOCSTRING (table, XFASTINT (category));
+}
+
+DEFUN ("get-unused-category", Fget_unused_category, Sget_unused_category,
+       0, 1, 0,
+  "Return a category which is not yet defined.\n\
+If total number of categories has reached the limit (95), return nil.\n\
+Optional argument specifies CATEGORY-TABLE,\n\
+ which defaults to the current buffer's category table.")
+  (table)
+     Lisp_Object table;
+{
+  int i;
+  Lisp_Object docstring_vector;
+
+  table = check_category_table (table);
+
+  for (i = ' '; i <= '~'; i++)
+    if (NILP (CATEGORY_DOCSTRING (table, i)))
+      return make_number (i);
+
+  return Qnil;
+}
+
+
+/* Category-table staff.  */
+
+DEFUN ("category-table-p", Fcategory_table_p, Scategory_table_p, 1, 1, 0,
+  "Return t if ARG is a category table.")
+  (arg)
+     Lisp_Object arg;
+{
+  if (CHAR_TABLE_P (arg)
+      && EQ (XCHAR_TABLE (arg)->purpose, Qcategory_table)
+      && CHAR_TABLE_EXTRA_SLOTS (XCHAR_TABLE (arg)) == 2)
+    return Qt;
+  return Qnil;
+}
+
+/* If TABLE is nil, return the current category table.  If TABLE is
+   not nil, check the validity of TABLE as a category table.  If
+   valid, return TABLE itself, but if not valid, signal an error of
+   wrong-type-argument.  */
+
+Lisp_Object
+check_category_table (table)
+     Lisp_Object table;
+{
+  register Lisp_Object tem;
+  if (NILP (table))
+    return current_buffer->category_table;
+  while (tem = Fcategory_table_p (table), NILP (tem))
+    table = wrong_type_argument (Qcategory_table_p, table);
+  return table;
+}   
+
+DEFUN ("category-table", Fcategory_table, Scategory_table, 0, 0, 0,
+  "Return the current category table.\n\
+This is the one specified by the current buffer.")
+  ()
+{
+  return current_buffer->category_table;
+}
+
+DEFUN ("standard-category-table", Fstandard_category_table,
+   Sstandard_category_table, 0, 0, 0,
+  "Return the standard category table.\n\
+This is the one used for new buffers.")
+  ()
+{
+  return Vstandard_category_table;
+}
+
+/* Return a copy of category table TABLE.  We can't simply use the
+   function copy-sequence because no contents should be shared between
+   the original and the copy.
+
+   If TOP is 1, we at first copy the tree structure of the table.  */
+
+Lisp_Object
+copy_category_table (table, top)
+     Lisp_Object table;
+{
+  int i;
+
+  if (top)
+    table = Fcopy_sequence (table);
+  else if (!NILP (XCHAR_TABLE (table)->defalt))
+    XCHAR_TABLE (table)->defalt
+      = Fcopy_sequence (XCHAR_TABLE (table)->defalt);
+
+  for (i = 0; i < CHAR_TABLE_ORDINARY_SLOTS; i++)
+    {
+      Lisp_Object idx = make_number (i);
+      Lisp_Object val = Faref (table, idx);
+
+      if (NILP (val))		/* Do nothing because we can share nil.  */
+	;
+      else if (CATEGORY_SET_P (val))
+	Faset (table, idx, Fcopy_sequence (val));
+      else if (CHAR_TABLE_P (val))
+	Faset (table, idx, copy_category_table (val, 0));
+      else			/* Invalid contents.  */
+	Faset (table, idx, Qnil);
+    }
+
+  return table;
+}
+
+DEFUN ("copy-category-table", Fcopy_category_table, Scopy_category_table,
+       0, 1, 0,
+  "Construct a new category table and return it.\n\
+It is a copy of the TABLE, which defaults to the standard category table.")
+  (table)
+     Lisp_Object table;
+{
+  if (!NILP (table))
+    check_category_table (table);
+  else
+    table = Vstandard_category_table;
+
+  return copy_category_table (table, 1);
+}
+
+DEFUN ("set-category-table", Fset_category_table, Sset_category_table, 1, 1, 0,
+  "Select a new category table for the current buffer.\n\
+One argument, a category table.")
+  (table)
+     Lisp_Object table;
+{
+  table = check_category_table (table);
+  current_buffer->category_table = table;
+  /* Indicate that this buffer now has a specified category table.  */
+  current_buffer->local_var_flags
+    |= XFASTINT (buffer_local_flags.category_table);
+  return table;
+}
+
+
+DEFUN ("char-category-set", Fchar_category_set, Schar_category_set, 1, 1, 0,
+  "Return a category set of CHAR.")
+  (ch)
+     Lisp_Object ch;
+{
+  Lisp_Object val;
+  int charset;
+  unsigned char c1, c2;
+
+  CHECK_NUMBER (ch, 0);
+  return CATEGORY_SET (XFASTINT (ch));
+}
+
+DEFUN ("category-set-mnemonics", Fcategory_set_mnemonics,
+       Scategory_set_mnemonics, 1, 1, 0,
+  "Return a string of mnemonics of all categories in CATEGORY-SET.")
+  (category_set)
+     Lisp_Object category_set;
+{
+  int i, j;
+  char str[96];
+
+  CHECK_CATEGORY_SET (category_set, 0);
+
+  j = 0;
+  for (i = 32; i < 127; i++)
+    if (CATEGORY_MEMBER (i, category_set))
+      str[j++] = i;
+  str[j] = '\0';
+
+  return build_string (str);
+}
+
+/* Modify all category sets stored under category table TABLE so that
+   they contain (SET_VALUE is t) or don't contain (SET_VALUE is nil)
+   CATEGORY.  */
+
+void
+modify_lower_category_set (table, category, set_value)
+     Lisp_Object table, category, set_value;
+{
+  Lisp_Object val;
+  int i;
+
+  if (NILP (XCHAR_TABLE (table)->defalt))
+    {
+      val = MAKE_CATEGORY_SET;
+      SET_CATEGORY_SET (val, category, set_value);
+      XCHAR_TABLE (table)->defalt = val;
+    }
+
+  for (i = 32; i < CHAR_TABLE_ORDINARY_SLOTS; i++)
+    {
+      val = XCHAR_TABLE (table)->contents[i];
+
+      if (CATEGORY_SET_P (val))
+	SET_CATEGORY_SET (val, category, set_value);
+      else if (CHAR_TABLE_P (val))
+	modify_lower_category_set (val, category, set_value);
+    }
+}
+
+void
+set_category_set (category_set, category, val)
+     Lisp_Object category_set, category, val;
+{
+  do {
+    int idx = XINT (category) / 8;
+    unsigned char bits = 1 << (XINT (category) % 8);
+
+    if (NILP (val))
+      XCATEGORY_SET (category_set)->data[idx] &= ~bits;
+    else
+      XCATEGORY_SET (category_set)->data[idx] |= bits;
+  } while (0);
+}
+
+DEFUN ("modify-category-entry", Fmodify_category_entry,
+       Smodify_category_entry, 2, 4, 0,
+  "Modify the category set of CHAR by adding CATEGORY to it.\n\
+The category is changed only for table TABLE, which defaults to\n\
+ the current buffer's category table.\n\
+If optional forth argument RESET is non NIL,\n\
+ CATEGORY is deleted from the category set instead of being added.")
+  (ch, category, table, reset)
+     Lisp_Object ch, category, table, reset;
+{
+  int c, charset, c1, c2;
+  Lisp_Object set_value;	/* Actual value to be set in category sets.  */
+  Lisp_Object val, category_set;
+
+  CHECK_NUMBER (ch, 0);
+  c = XINT (ch);
+  CHECK_CATEGORY (category, 1);
+  table = check_category_table (table);
+
+  if (NILP (CATEGORY_DOCSTRING (table, XFASTINT (category))))
+    error ("Undefined category: %c", XFASTINT (category));
+  
+  set_value = NILP (reset) ? Qt : Qnil;
+
+  if (SINGLE_BYTE_CHAR_P (c))
+    {
+      val = XCHAR_TABLE (table)->contents[c];
+      if (!CATEGORY_SET_P (val))
+	XCHAR_TABLE (table)->contents[c] = (val = MAKE_CATEGORY_SET);
+      SET_CATEGORY_SET (val, category, set_value);
+      return Qnil;
+    }
+
+  if (COMPOSITE_CHAR_P (c))
+    c = cmpchar_component (c, 0);
+  SPLIT_NON_ASCII_CHAR (c, charset, c1, c2);
+
+  /* The top level table.  */
+  val = XCHAR_TABLE (table)->contents[charset];
+  if (NILP (val))
+    {
+      category_set = MAKE_CATEGORY_SET;
+      XCHAR_TABLE (table)->contents[charset] = category_set;
+    }
+  else if (CATEGORY_SET_P (val))
+    category_set = val;
+
+  if (!c1)
+    {
+      /* Only a charset is specified.  */
+      if (CHAR_TABLE_P (val))
+	/* All characters in CHARSET should be the same as for CATEGORY.  */
+	modify_lower_category_set (val, category, set_value);
+      else
+	SET_CATEGORY_SET (category_set, category, set_value);
+      return Qnil;
+    }
+
+  /* The second level table.  */
+  if (!CHAR_TABLE_P (val))
+    {
+      val = Fmake_char_table (Qnil, Qnil);
+      XCHAR_TABLE (table)->contents[charset] = val;
+      /* We must set default category set of CHARSET in `defalt' slot.  */
+      XCHAR_TABLE (val)->defalt = category_set;
+    }
+  table = val;
+
+  val = XCHAR_TABLE (table)->contents[c1];
+  if (NILP (val))
+    {
+      category_set = Fcopy_sequence (XCHAR_TABLE (table)->defalt);
+      XCHAR_TABLE (table)->contents[c1] = category_set;
+    }
+  else if (CATEGORY_SET_P (val))
+    category_set = val;
+
+  if (!c2)
+    {
+      if (CHAR_TABLE_P (val))
+	/* All characters in C1 group of CHARSET should be the same as
+           for CATEGORY.  */
+	modify_lower_category_set (val, category, set_value);
+      else
+	SET_CATEGORY_SET (category_set, category, set_value);
+      return Qnil;
+    }
+
+  /* The third (bottom) level table.  */
+  if (!CHAR_TABLE_P (val))
+    {
+      val = Fmake_char_table (Qnil, Qnil);
+      XCHAR_TABLE (table)->contents[c1] = val;
+      /* We must set default category set of CHARSET and C1 in
+         `defalt' slot.  */
+      XCHAR_TABLE (val)->defalt = category_set;
+    }
+  table = val;
+
+  val = XCHAR_TABLE (table)->contents[c2];
+  if (NILP (val))
+    {
+      category_set = Fcopy_sequence (XCHAR_TABLE (table)->defalt);
+      XCHAR_TABLE (table)->contents[c2] = category_set;
+    }
+  else if (CATEGORY_SET_P (val))
+    category_set = val;
+  else
+    /* This should never happen.  */
+    error ("Invalid category table");
+
+  SET_CATEGORY_SET (category_set, category, set_value);
+
+  return Qnil;
+}
+
+/* Dump category table to buffer in human-readable format */
+
+static void
+describe_category (value)
+    Lisp_Object value;
+{
+  Lisp_Object mnemonics;
+
+  Findent_to (make_number (16), make_number (1));
+
+  if (NILP (value))
+    {
+      insert_string ("default\n");
+      return;
+    }
+
+  if (!CATEGORY_SET_P (value))
+    {
+      insert_string ("invalid\n");
+      return;
+    }
+
+  mnemonics = Fcategory_set_mnemonics (value);
+  insert_from_string (mnemonics, 0, XSTRING (mnemonics)->size, 0);
+  insert_string ("\n");
+  return;
+}
+
+static Lisp_Object
+describe_category_1 (vector)
+     Lisp_Object vector;
+{
+  struct buffer *old = current_buffer;
+  set_buffer_internal (XBUFFER (Vstandard_output));
+  describe_vector (vector, Qnil, describe_category, 0, Qnil, Qnil);
+  {
+    int i;
+    Lisp_Object docs = XCHAR_TABLE (vector)->extras[0];
+    Lisp_Object elt;
+
+    if (!VECTORP (docs) || XVECTOR (docs)->size != 95)
+      {
+	insert_string ("Invalid first extra slot in this char table\n");
+	return Qnil;
+      }
+      
+    insert_string ("Meanings of mnemonice characters are:\n");
+    for (i = 0; i < 95; i++)
+      {
+	elt = XVECTOR (docs)->contents[i];
+	if (NILP (elt))
+	  continue;
+
+	insert_char (i + 32);
+	insert (": ", 2);
+	insert_from_string (elt, 0, XSTRING (elt)->size, 0);
+	insert ("\n", 1);
+      }
+  }
+
+  while (! NILP (XCHAR_TABLE (vector)->parent))
+    {
+      vector = XCHAR_TABLE (vector)->parent;
+      insert_string ("\nThe parent category table is:");
+      describe_vector (vector, Qnil, describe_category, 0, Qnil, Qnil);
+    }
+
+  call0 (intern ("help-mode"));
+  set_buffer_internal (old);
+  return Qnil;
+}
+
+DEFUN ("describe-category", Fdescribe_category, Sdescribe_category, 0, 0, "",
+  "Describe the category specifications in the category table.\n\
+The descriptions are inserted in a buffer, which is then displayed.")
+  ()
+{
+  internal_with_output_to_temp_buffer
+     ("*Help*", describe_category_1, current_buffer->category_table);
+
+  return Qnil;
+}
+
+/* Return 1 if there is a word boundary between two word-constituent
+   characters C1 and C2 if they appear in this order, else return 0.
+   Use the macro WORD_BOUNDARY_P instead of calling this function
+   directly.  */
+
+int
+word_boundary_p (c1, c2)
+     int c1, c2;
+{
+  Lisp_Object category_set1, category_set2;
+  Lisp_Object tail;
+  int default_result;
+
+  if (CHAR_CHARSET (c1) == CHAR_CHARSET (c2))
+    {
+      tail = Vword_separating_categories;
+      default_result = 0;
+    }
+  else
+    {
+      tail = Vword_combining_categories;
+      default_result = 1;
+    }
+
+  category_set1 = CATEGORY_SET (c1);
+  if (NILP (category_set1))
+    return default_result;
+  category_set2 = CATEGORY_SET (c2);
+  if (NILP (category_set2))
+    return default_result;
+
+  for (; CONSP (tail); tail = XCONS (tail)->cdr)
+    {
+      Lisp_Object elt = XCONS(tail)->car;
+
+      if (CONSP (elt)
+	  && CATEGORYP (XCONS (elt)->car)
+	  && CATEGORYP (XCONS (elt)->cdr)
+	  && CATEGORY_MEMBER (XCONS (elt)->car, category_set1)
+	  && CATEGORY_MEMBER (XCONS (elt)->cdr, category_set2))
+	return !default_result;
+    }
+  return default_result;
+}
+
+
+init_category_once ()
+{
+  /* This has to be done here, before we call Fmake_char_table.  */
+  Qcategory_table = intern ("category-table");
+  staticpro (&Qcategory_table);
+
+  /* Intern this now in case it isn't already done.
+     Setting this variable twice is harmless.
+     But don't staticpro it here--that is done in alloc.c.  */
+  Qchar_table_extra_slots = intern ("char-table-extra-slots");
+
+  /* Now we are ready to set up this property, so we can
+     create category tables.  */
+  Fput (Qcategory_table, Qchar_table_extra_slots, make_number (2));
+
+  Vstandard_category_table = Fmake_char_table (Qcategory_table, Qnil);
+  /* Set a category set which contains nothing to the default.  */ 
+  XCHAR_TABLE (Vstandard_category_table)->defalt = MAKE_CATEGORY_SET;
+  Fset_char_table_extra_slot (Vstandard_category_table, 0,
+			      Fmake_vector (make_number (95), Qnil));
+}
+
+syms_of_category ()
+{
+  Qcategoryp = intern ("categoryp");
+  staticpro (&Qcategoryp);
+  Qcategorysetp = intern ("categorysetp");
+  staticpro (&Qcategorysetp);
+  Qcategory_table_p = intern ("category-table-p");
+  staticpro (&Qcategory_table_p);
+
+  DEFVAR_LISP ("word-combining-categories", &Vword_combining_categories,
+    "List of pair (cons) of categories to determine word boundary.\n\
+\n\
+Emacs treats a sequence of word constituent characters as a single\n\
+word (i.e. finds no word boundary between them) iff they belongs to\n\
+the same charset.  But, exceptions are allowed in the following cases.\n\
+\n\
+(1) The case that characters are in different charsets is controlled\n\
+by the variable `word-combining-categories'.\n\
+\n\
+Emacs finds no word boundary between characters of different charsets\n\
+if they have categories matching some element of this list.\n\
+\n\
+More precisely, if an element of this list is a cons of category CAT1\n\
+and CAT2, and a multibyte character C1 which has CAT1 is followed by\n\
+C2 which has CAT2, there's no word boundary between C1 and C2.\n\
+\n\
+For instance, to tell that ASCII characters and Latin-1 characters can\n\
+form a single word, the element `(?l . ?l)' should be in this list\n\
+because both characters have the category `l' (Latin characters).\n\
+\n\
+(2) The case that character are in the same charset is controlled by\n\
+the variable `word-separating-categories'.\n\
+\n\
+Emacs find a word boundary between characters of the same charset\n\
+if they have categories matching some element of this list.\n\
+\n\
+More precisely, if an element of this list is a cons of category CAT1\n\
+and CAT2, and a multibyte character C1 which has CAT1 is followed by\n\
+C2 which has CAT2, there's a word boundary between C1 and C2.\n\
+\n\
+For instance, to tell that there's a word boundary between Japanese\n\
+Hiragana and Japanese Kanji (both are in the same charset), the\n\
+element `(?H . ?C) should be in this list.");
+
+  Vword_combining_categories = Qnil;
+
+  DEFVAR_LISP ("word-separating-categories", &Vword_separating_categories,
+    "List of pair (cons) of categories to determine word boundary.\n\
+See the documentation of the variable `word-combining-categories'.");
+
+  Vword_separating_categories = Qnil;
+
+  defsubr (&Smake_category_set);
+  defsubr (&Sdefine_category);
+  defsubr (&Scategory_docstring);
+  defsubr (&Sget_unused_category);
+  defsubr (&Scategory_table_p);
+  defsubr (&Scategory_table);
+  defsubr (&Sstandard_category_table);
+  defsubr (&Scopy_category_table);
+  defsubr (&Sset_category_table);
+  defsubr (&Schar_category_set);
+  defsubr (&Scategory_set_mnemonics);
+  defsubr (&Smodify_category_entry);
+  defsubr (&Sdescribe_category);
+
+  category_table_version = 0;
+}