-- Module : string_pkg.ada -- Component of : common_library -- Version : 1.2 -- Date : 11/21/86 16:35:20 -- SCCS File : disk21~/rschm/hasee/sccs/common_library/sccs/sxstring_pkg.ada -- $Source: /nosc/work/abstractions/string/RCS/string.bdy,v $ -- $Revision: 1.3 $ -- $Date: 85/02/01 10:58:51 $ -- $Author: ron $ -- $Source: /nosc/work/abstractions/string/RCS/string.bdy,v $ -- $Revision: 1.3 $ -- $Date: 85/02/01 10:58:51 $ -- $Author: ron $ with unchecked_deallocation; with lists, stack_pkg; package body string_pkg is SCCS_ID : constant String := "@(#) string_pkg.ada, Version 1.2"; --| Overview: --| The implementation for most operations is fairly straightforward. --| The interesting aspects involve the allocation and deallocation of --| heap space. This is done as follows: --| --| 1. A stack of accesses to lists of string_type values is set up --| so that the top of the stack always refers to a list of values --| that were allocated since the last invocation of mark. --| The stack is called scopes, referring to the dynamic scopes --| defined by the invocations of mark and release. --| There is an implicit invocation of mark when the --| package body is elaborated; this is implemented with an explicit --| invocation in the package initialization code. --| --| 2. At each invocation of mark, a pointer to an empty list --| is pushed onto the stack. --| --| 3. At each invocation of release, all of the values in the --| list referred to by the pointer at the top of the stack are --| returned to the heap. Then the list, and the pointer to it, --| are returned to the heap. Finally, the stack is popped. package string_list_pkg is new lists(string_type); subtype string_list is string_list_pkg.list; type string_list_ptr is access string_list; package scope_stack_pkg is new stack_pkg(string_list_ptr); subtype scope_stack is scope_stack_pkg.stack; use string_list_pkg; use scope_stack_pkg; scopes: scope_stack; -- See package body overview. -- Utility functions/procedures: function enter(s: string_type) return string_type; --| Raises: illegal_alloc --| Effects: --| Stores s, the address of s.all, in current scope list (top(scopes)), --| and returns s. Useful for functions that create and return new --| string_type values. --| Raises illegal_alloc if the scopes stack is empty. function match_string(s1, s2: string; start: positive := 1) return natural; --| Raises: no_match --| Effects: --| Returns the minimum index, i, in s1'range such that --| s1(i..i + s2'length - 1) = s2. Returns 0 if no such index. --| Requires: --| s1'first = 1. -- Constructors: function Empty_String return String_Type is begin return Create (""); end Empty_String; function create(s: string) return string_type is subtype constr_str is string(1..s'length); dec_s: constr_str := s; begin return enter(new constr_str'(dec_s)); -- DECada bug; above code (and decl of dec_s) replaces the following: -- return enter(new constr_str'(s)); end create; function "&"(s1, s2: string_type) return string_type is begin if is_empty(s1) then return enter(make_persistent(s2)); end if; if is_empty(s2) then return enter(make_persistent(s1)); end if; return create(s1.all & s2.all); end "&"; function "&"(s1: string_type; s2: string) return string_type is begin if s1 = null then return create(s2); end if; return create(s1.all & s2); end "&"; function "&"(s1: string; s2: string_type) return string_type is begin if s2 = null then return create(s1); end if; return create(s1 & s2.all); end "&"; function substr(s: string_type; i: positive; len: natural) return string_type is begin if len = 0 then return null; end if; return create(s(i..(i + len - 1))); exception when constraint_error => -- on array fetch or null deref raise bounds; end substr; function splice(s: string_type; i: positive; len: natural) return string_type is begin if len = 0 then return enter(make_persistent(s)); end if; if i + len - 1 > length(s) then raise bounds; end if; return create(s(1..(i - 1)) & s((i + len)..length(s))); end splice; function insert(s1, s2: string_type; i: positive) return string_type is begin if i > length(s1) then raise bounds; end if; if is_empty(s2) then return create(s1.all); end if; return create(s1(1..(i - 1)) & s2.all & s1(i..s1'last)); end insert; function insert(s1: string_type; s2: string; i: positive) return string_type is begin if i > length(s1) then raise bounds; end if; return create(s1(1..(i - 1)) & s2 & s1(i..s1'last)); end insert; function insert(s1: string; s2: string_type; i: positive) return string_type is begin if not (i in s1'range) then raise bounds; end if; if s2 = null then return create(s1); end if; return create(s1(s1'first..(i - 1)) & s2.all & s1(i..s1'last)); end insert; procedure lc(c: in out character) is begin if ('A' <= c) and then (c <= 'Z') then c := character'val(character'pos(c) - character'pos('A') + character'pos('a')); end if; end lc; procedure uc(c: in out character) is begin if ('a' <= c) and then (c <= 'z') then c := character'val(character'pos(c) - character'pos('a') + character'pos('A')); end if; end uc; function lower(s: string) return string_type is s2: string_type := create(s); begin for i in s2'range loop lc(s2(i)); end loop; return s2; end lower; function lower(s: string_type) return string_type is begin if s = null then return null; end if; return lower(s.all); end lower; function Mixed (S: String) return String_Type is Mixed_String : String_Type := Create (S); begin if Mixed_String'Length /= 0 then UC (Mixed_String(Mixed_String'First)); for i in Mixed_String'First + 1 .. Mixed_String'Last loop if Mixed_String(i-1) = '_' then UC (Mixed_String(i)); else LC (Mixed_String(i)); end if; end loop; return Mixed_String; else return Empty_String; end if; end Mixed; function Mixed (S : String_Type) return String_Type is begin if s = null then return Empty_String; else return Mixed (S.all); end if; end Mixed; function upper(s: string) return string_type is s2: string_type := create(s); begin for i in s2'range loop uc(s2(i)); end loop; return s2; end upper; function upper(s: string_type) return string_type is begin if s = null then return null; end if; return upper(s.all); end upper; -- Heap Management: function make_persistent(s: string_type) return string_type is subtype constr_str is string(1..length(s)); begin if s = null or else s.all = "" then return null; else return new constr_str'(s.all); end if; end make_persistent; function make_persistent(s: string) return string_type is subtype constr_str is string(1..s'length); begin if s = "" then return null; else return new constr_str'(s); end if; end make_persistent; procedure real_flush is new unchecked_deallocation(string, string_type); --| Effect: --| Return space used by argument to heap. Does nothing if null. --| Notes: --| This procedure is actually the body for the flush procedure, --| but a generic instantiation cannot be used as a body for another --| procedure. You tell me why. procedure flush(s: in out string_type) is begin if s /= null then real_flush(s); end if; -- Actually, the if isn't needed; however, DECada compiler chokes -- on deallocation of null. end flush; procedure mark is begin push(scopes, new string_list'(create)); end mark; procedure release is procedure flush_list_ptr is new unchecked_deallocation(string_list, string_list_ptr); iter: string_list_pkg.ListIter; top_list: string_list_ptr; s: string_type; begin pop(scopes, top_list); iter := MakeListIter(top_list.all); while more(iter) loop next(iter, s); flush(s); -- real_flush is bad, DECada bug -- real_flush(s); end loop; destroy(top_list.all); flush_list_ptr(top_list); exception when empty_stack => raise illegal_dealloc; end release; -- Queries: function is_empty(s: string_type) return boolean is begin return (s = null) or else (s.all = ""); end is_empty; function length(s: string_type) return natural is begin if s = null then return 0; end if; return(s.all'length); end length; function value(s: string_type) return string is subtype null_range is positive range 1..0; subtype null_string is string(null_range); begin if s = null then return null_string'(""); end if; return s.all; end value; function fetch(s: string_type; i: positive) return character is begin if is_empty(s) or else (not (i in s'range)) then raise bounds; end if; return s(i); end fetch; function equal(s1, s2: string_type) return boolean is begin if is_empty(s1) then return is_empty(s2); end if; return (s2 /= null) and then (s1.all = s2.all); -- The above code replaces the following. (DECada buggy) -- return s1.all = s2.all; -- exception -- when constraint_error => -- s is null -- return is_empty(s1) and is_empty(s2); end equal; function equal(s1: string_type; s2: string) return boolean is begin if s1 = null then return s2 = ""; end if; return s1.all = s2; end equal; function equal(s1: string; s2: string_type) return boolean is begin if s2 = null then return s1 = ""; end if; return s1 = s2.all; end equal; --|======================================================================== function Equivalent (Left, Right : in String_Type) return Boolean is begin return Equal (Upper (Left), Upper (Right)); end Equivalent; function Equivalent (Left : in String; Right : in String_Type) return Boolean is begin return Equivalent (Create (Left), Right); end Equivalent; function Equivalent (Left : in String_Type; Right : in String) return Boolean is begin return Equivalent (Left, Create (Right)); end Equivalent; --|======================================================================== function "<" (s1: string_type; s2: string_type) return boolean is begin if is_empty(s1) then return (not is_empty(s2)); else return (s1.all < s2); end if; -- Got rid of the following code: (Think that DECada is buggy) --return s1.all < s2.all; --exception --when constraint_error => -- on null deref --return (not is_empty(s2)); -- one of them must be empty end "<"; function "<"(s1: string_type; s2: string) return boolean is begin if s1 = null then return s2 /= ""; end if; return s1.all < s2; end "<"; function "<"(s1: string; s2: string_type) return boolean is begin if s2 = null then return false; end if; return s1 < s2.all; end "<"; function "<="(s1: string_type; s2: string_type) return boolean is begin if is_empty(s1) then return true; end if; return (s1.all <= s2); -- Replaces the following: (I think DECada is buggy) --return s1.all <= s2.all; --exception --when constraint_error => -- on null deref --return is_empty(s1); -- one must be empty, so s1<=s2 iff s1 = "" end "<="; function "<="(s1: string_type; s2: string) return boolean is begin if s1 = null then return true; end if; return s1.all <= s2; end "<="; function "<="(s1: string; s2: string_type) return boolean is begin if s2 = null then return s1 = ""; end if; return s1 <= s2.all; end "<="; function match_c(s: string_type; c: character; start: positive := 1) return natural is begin if s = null then return 0; end if; for i in start..s.all'last loop if s(i) = c then return i; end if; end loop; return 0; end match_c; function match_not_c(s: string_type; c: character; start: positive := 1) return natural is begin if s = null then return 0; end if; for i in start..s.all'last loop if s(i) /= c then return i; end if; end loop; return 0; end match_not_c; function match_s(s1, s2: string_type; start: positive := 1) return natural is begin if (s1 = null) or else (s2 = null) then return 0; end if; return match_string(s1.all, s2.all, start); end match_s; function match_s(s1: string_type; s2: string; start: positive := 1) return natural is begin if s1 = null then return 0; end if; return match_string(s1.all, s2, start); end match_s; function match_any(s, any: string_type; start: positive := 1) return natural is begin if any = null then raise any_empty; end if; return match_any(s, any.all, start); end match_any; function match_any(s: string_type; any: string; start: positive := 1) return natural is begin if any = "" then raise any_empty; end if; if s = null then return 0; end if; for i in start..s.all'last loop for j in any'range loop if s(i) = any(j) then return i; end if; end loop; end loop; return 0; end match_any; function match_none(s, none: string_type; start: positive := 1) return natural is begin if is_empty(s) then return 0; end if; if is_empty(none) then return 1; end if; return match_none(s, none.all, start); end match_none; function match_none(s: string_type; none: string; start: positive := 1) return natural is found: boolean; begin if is_empty(s) then return 0; end if; for i in start..s.all'last loop found := true; for j in none'range loop if s(i) = none(j) then found := false; exit; end if; end loop; if found then return i; end if; end loop; return 0; end match_none; -- Utilities: function enter(s: string_type) return string_type is begin -- ATTACHING TO THE END OF THE LIST IS ENTIRELY TOO SLOW AND UNNECESSARY -- top(scopes).all := attach(top(scopes).all, s); top(scopes).all := attach(s, top(scopes).all); return s; exception when empty_stack => raise illegal_alloc; end enter; function match_string(s1, s2: string; start: positive := 1) return natural is offset: natural; begin offset := s2'length - 1; for i in start..(s1'last - offset) loop if s1(i..(i + offset)) = s2 then return i; end if; end loop; return 0; exception when constraint_error => -- on offset := s2'length (= 0) return 0; end match_string; begin -- Initialize the scopes stack with an implicit mark. scopes := create; mark; end string_pkg;