DEFINITION Attributes; (* portable *)

(*Module Attributes manages the attribute lists of the gadgets, provides an
improved scanner with macro substitution facilities for executing commands,
and supplies type conversion routines.
*)
 IMPORT
  Objects, Texts, Files;

 CONST
  (* Scanner symbol classes. *)
  Inval = Texts.Inval;           (* Invalid symbol. *)
  Name = Texts.Name;        (* Name. *)
  String = Texts.String;        (* Literal string. *)
  Int = Texts.Int;             (* Integer i. *)
  Real = Texts.Real;          (* Real number x. *)
  LongReal = Texts.LongReal;  (* Long real number y. *)
  Char = Texts.Char;          (* Special character c. *)
  Obj = Texts.Object;      (* Object o. *)

 TYPE
  Reader = POINTER TO ReaderDesc;  (* Macro substituting reader. *)
  ReaderDesc = RECORD
   substitute: BOOLEAN;  (* Is substitution on or off? *)
   text: Texts.Text;  (* Current text read. *)
   eot: BOOLEAN;  (* End of text reached? *)
   lib: Objects.Library; (* Library of last character/object read. *)
  END;

(* Upcall for macro substitution. Ch is the character to be substituted, res
is the substitution text and beg is the starting position inside of the text.*)
  MacroHandler = PROCEDURE (ch: CHAR; VAR T: Reader; VAR res: Texts.Text; VAR beg: LONGINT);
  Scanner = RECORD (* Macro substituting scanner *)
   R: Reader;  (* Scanner operates with this reader. *)
   eot: BOOLEAN; (* End of text reached? *)
   nextCh: CHAR; (* Character located immediately after scanned token. *)
   class: INTEGER;  (* Scanner classes. Scanned tokens are returned in the
record fields below. *)
   i: LONGINT;
   x: REAL;
   y: LONGREAL;
   c: CHAR;
   len: SHORTINT;
   s: ARRAY  128 OF CHAR;
   o: Objects.Object;
  END;

 (* Data structures for storing attribute lists. *)
  Attr = POINTER TO AttrDesc;
  AttrDesc = RECORD
   next: Attr;
   name: Objects.Name
  END;

  BoolAttr = POINTER TO BoolDesc;
  BoolDesc = RECORD ( AttrDesc ) 
   b: BOOLEAN END;

  CharAttr = POINTER TO CharDesc;
  CharDesc = RECORD ( AttrDesc ) 
   c: CHAR END;

  IntAttr = POINTER TO IntDesc;
  IntDesc = RECORD ( AttrDesc ) 
   i: LONGINT END;

  RealAttr = POINTER TO RealDesc;
  RealDesc = RECORD ( AttrDesc ) 
   r: LONGREAL END;

  StringAttr = POINTER TO StringDesc;
  StringDesc = RECORD ( AttrDesc ) 
   s: ARRAY 64 OF CHAR; END;

(* Convert a string to a text. *)
 PROCEDURE StrToTxt (s: ARRAY OF CHAR; VAR T: Texts.Text);

(* Convert a text to a string. The string might be terminated early if the text
is too long to fit. *)
 PROCEDURE TxtToStr (T: Texts.Text; VAR s: ARRAY OF CHAR);

(* Read character ch from the Reader. Registered character macros are automatically
substituted by making upcalls to the installed macro handlers. *)
 PROCEDURE Read (VAR R: Reader; VAR ch: CHAR);

(* Open reader R at position pos in text. *)
 PROCEDURE OpenReader (VAR R: Reader; text: Texts.Text; pos: LONGINT);

(* Return current position of Reader R in text R.text. Note that R.text may
change as macro characters are being substituted. *)
 PROCEDURE Pos (VAR R: Reader): LONGINT;

(* Open Scanner S at position pos in text T. *)
 PROCEDURE OpenScanner (VAR S: Scanner; T: Texts.Text; pos: LONGINT);

(* Read the next symbol or object in the text. White space is ignored. *)
 PROCEDURE Scan (VAR S: Scanner);

(* Register a macro handler for a character. This handler is called when character
ch is read using the reader/scanner, and must return a text with the substitution.
*)
 PROCEDURE AddMacro (ch: CHAR; handler: MacroHandler);

(* Store the atttribute list A. *)
 PROCEDURE StoreAttributes (VAR R: Files.Rider; A: Attr);

(* Load attribute list resulting in a list A. *)
 PROCEDURE LoadAttributes (VAR R: Files.Rider; VAR A: Attr);

(* Copy an attribute list. *)
 PROCEDURE CopyAttributes (in: Attr; VAR out: Attr);

(* Insert an attribute in a list. An existing attribute with the same name is
discarded. *)
 PROCEDURE InsertAttr (VAR list: Attr; name: ARRAY OF CHAR; val: Attr);

(* Search for an attribute name in list. *)
 PROCEDURE FindAttr (name: ARRAY OF CHAR; list: Attr): Attr;

(* Delete an attribute. *)
 PROCEDURE DeleteAttr (VAR list: Attr; name: ARRAY OF CHAR);

(* Write the attribute attr of object obj to the writer W. Format conversion
to strings are automatic.*)
 PROCEDURE WriteAttr (obj: Objects.Object; attr: ARRAY OF CHAR; VAR W: Texts.Writer);

(* GetXXX(obj: Objects.Object; name: ARRAY OF CHAR; VAR x: T);
  Retrieve object attribute name and convert it to type T.
  The following conversions are done by GetType:
 Type T Attribute classes converted

 Bool BOOLEAN Bool, String, Char
 Int LONGINT Int, String, Real, LongReal
 Real REAL Real, String, LongReal, Int
 LongReal LONGREAL LongReal, String, Real, Int
 String ARRAY OF CHAR String, Int, Bool, Real, LongReal, Bool
*)
 PROCEDURE GetBool (obj: Objects.Object; name: ARRAY OF CHAR; VAR b: BOOLEAN);
 PROCEDURE GetInt (obj: Objects.Object; name: ARRAY OF CHAR; VAR i: LONGINT);
 PROCEDURE GetReal (obj: Objects.Object; name: ARRAY OF CHAR; VAR x: REAL);
 PROCEDURE GetLongReal (obj: Objects.Object; name: ARRAY OF CHAR; VAR y: LONGREAL);
 PROCEDURE GetString (obj: Objects.Object; name: ARRAY OF CHAR; VAR s: ARRAY OF CHAR);

(* SetXXX(obj: Objects.Object; name: ARRAY OF CHAR; x: T);
  Set object attribute name and convert it to the the attribute class understood
by obj.
  The following conversions are done by SetType:
 Type T Attribute classes converted

 Bool BOOLEAN Bool, String, Char
 Int LONGINT Int, String, Real, LongReal
 Real REAL Real, String, LongReal, Int
 LongReal LONGREAL LongReal, String, Real, Int
 String ARRAY OF CHAR String, Int, Bool, Real, LongReal, Bool
*)
 PROCEDURE SetBool (obj: Objects.Object; name: ARRAY OF CHAR; b: BOOLEAN);
 PROCEDURE SetInt (obj: Objects.Object; name: ARRAY OF CHAR; i: LONGINT);
 PROCEDURE SetReal (obj: Objects.Object; name: ARRAY OF CHAR; x: REAL);
 PROCEDURE SetLongReal (obj: Objects.Object; name: ARRAY OF CHAR; y: LONGREAL);
 PROCEDURE SetString (obj: Objects.Object; name, s: ARRAY OF CHAR);

(* Write all parameters of command. *)
 PROCEDURE Echo;

END Attributes.

(*
Remarks:

1. Reader and Scanner
The reader and scanner operate in the same fashion as that of the Text module.
There are however a few exceptions. First, the reader does macro substitution
as texts are read. Some macros are predefined, and the programmer has the capability
to add his or her own macros by identifying special symbols for macros and a
handler for that macro symbol. While reading or scanning a text, upcalls are
made to the registered macro handler to return a substitution text for the macro
symbol. New macros are registered with the AddMacro procedure. The macro handler
has to return a text and a position in the text where reading/scanning should
continue. Reading/scanning will continue in the original text after the end
of the substitution text is reached. The macro might take parameters (letters
that follow immediately after the macro symbol), which are read by the macro
handler using the passed Reader. Note that no substitution is made when no text
(= NIL) is returned. By default, the up arrow ("^"), which expands to the current
selection, is installed as a macro in the Attributes module. In contrast to
the Texts.Scanner, the Attributes.Scanner scan words containing letters like
ä, ü, ö,  etc, and (non-character) objects embedded inside of the text.

2. Attribute Message and Attribute Storage
Most gadgets employ two strategies for storing attribute values. The first is
by allocating own storage space for the attributes in the object definition
and by responding on the Objects.AttrMsg when these attributes are accessed.
The second way is having the default message handlers of module Gadgets take
care of attributes. This is called the default or standard handling of attributes.
The default message handlers manage lists of gadgets with the types defined
in module Attributes. Such an attribute list is always identified by its first
component, which might change when attributes are inserted or deleted. Many
gadgets uses a hybrid approach to attribute handling, where own attributes are
handled in a special way, and all other attributes are handled by the default
message handlers. For example, the "Name" of a gadget is typically handled by
the default message handlers. This has the advantage that storage space is only
used when the attribute has a value (remember that many gadgets don't have names,
and allocating space inside your own gadget record descriptor for a name, makes
you pay the storage price for each gadget, even if it is not named).

*)