source.sa
Generated by gen_html_sa_files from ICSI. Contact gomes@icsi.berkeley.edu for details
-------------------------> GNU Sather - sourcefile <-------------------------
-- Copyright (C) 2000 by K Hopper, University of Waikato, New Zealand --
-- This file is part of the GNU Sather library. It is free software; you may --
-- redistribute and/or modify it under the terms of the GNU Library General --
-- Public License (LGPL) as published by the Free Software Foundation; --
-- either version 2 of the license, or (at your option) any later version. --
-- This library 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 Doc/LGPL for more details. --
-- The license text is also available from: Free Software Foundation, Inc., --
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --
--------------> Please email comments to <bug-sather@gnu.org> <--------------
immutable class CONDITIONALS < $ENUMS{CONDITIONALS}
immutable class CONDITIONALS < $ENUMS{CONDITIONALS} is
-- This immutable class is the enumeration over the conditional source
-- reading keywords.
--
-- The corresponding names in the culture-independent resources file
-- are prescribed by the ISO/IEC standard 14652. The required names are --
--
-- def, ifdef, ifdef, ifndef, elsif, else, endif
-- Version 1.0 Feb 98. Copyright K Hopper, U of Waikato
-- Development History
-- -------------------
-- Date Who By Detail
-- ---- ------ ------
-- 19 Feb 98 kh Original from ISO/IEC 9945-2
include EXACT_ENUM{CONDITIONALS} ;
private const val_count : CARD := 7 ;
-- The following constant 'routines' specify the enumeration values.
Define : SAME is return enum(1) end ;
Undefine : SAME is return enum(2) end ;
If_Defined : SAME is return enum(3) end ;
If_Not_Defined : SAME is return enum(4) end ;
Else_If : SAME is return enum(5) end ;
Else : SAME is return enum(6) end ;
End_If : SAME is return enum(7) end ;
end ; -- CONDITIONALS
class SOURCE
class SOURCE is
-- This class defines the abstraction of a text source from which lines
-- can be taken in sequence.
--
-- A source is created from a text file whose name is provided to the
-- creation routine. The major part of the class is the pre-processed and
-- raw iters for returning lines (as string cursors) to the using class.
--
--
-- Version 1.1 Oct 98. Copyright K Hopper, U of Waikato
-- Development History
-- -------------------
-- Date Who By Detail
-- ---- ------ ------
-- 9 Jun 98 kh Original from boot cultural compiler.
-- 26 Oct 98 kh Revised , added pre/post conditions
private attr escape_char : CHAR ;
readonly attr file_name : FILE_PATH ;
private attr fyle_cursor : STR_CURSOR ;
readonly attr line_no : CARD ;
private attr stack : FLIST{BOOL} ;
private attr else_seen : FLIST{BOOL} ;
private attr nesting_valid : BOOL ;
private attr toggles : FSET{STR} ;
private shared report : REPORTER ;
private const
File_Path, File_Access, Bad_Nesting, Missing_Toggle,
Dup_Name, Unknown ;
private const Msg_Count : CARD := Unknown + 1 ;
private shared Messages : ARRAY{STR} ;
private init(me : SAME)
pre ~void(me)
post ~void(Messages)
and ~void(report)
is
-- This private routine initialises those components which are shared
-- value objects -- if necessary.
if void(report) then -- sets up shared once only!
loc_cult : CULTURE := CULTURE::default ;
Messages := loc_cult.resources.read(SYS::rune_name(me),Msg_Count) ;
report := REPORTER::create(Messages,loc_cult.sather_lib)
end
end ;
create(fname : STR) : SAME
pre (fname.size > 0)
post true
is
-- This routine attempts to open the file with the given name for
-- reading and, if successful, reads its contents, closes the file and then
-- creates the fyle_cursor for use when yielding individual lines! If the
-- file is not found or may not be read then void is returned after issuing
-- an error message.
--
-- NOTE The default comment start - ' ' - is established.
fyle : TEXT_FILE ;
me : SAME := new ;
init(me) ;
loc_path : FILE_PATH := FILE_PATH::create(fname) ;
if loc_path.leaf = fname then --a relative name!
loc_path := FILE_PATH::create(
DIRECTORY::current.str).append(loc_path)
end ;
if void(loc_path) then
report.error(File_Path,fname) ;
return void
else
me.file_name := loc_path ;
fyle := TEXT_FILE::open_for_read(loc_path.str) ;
if void(fyle)
or fyle.error then -- couldn't find the file?
report.error(File_Access,fname) ;
return void
end
end ;
me.file_name := loc_path ;
me.fyle_cursor := fyle.fstr.str.cursor ;
fyle.close ;
-- The next three lines set up for the pre-processing iter.
me.nesting_valid := true ; -- in case first line is not cond!
me.stack := FLIST{BOOL}::create ;
me.else_seen := FLIST{BOOL}::create ;
me.toggles := FSET{STR}::create ;
me.escape_char := fname.index_lib.Null.char ; -- ie there isn't one!
me.line_no := 1 ;
return me
end ;
create(
fpath : FILE_PATH
) : SAME
pre ~void(fpath)
post true
is
-- This routine attempts to open the file with the given path for
-- reading and, if successful, reads its contents, closes the file and then
-- creates the fyle_cursor for use when yielding individual lines! If the
-- file is not found or may not be read then void is returned.
me : SAME := new ;
init(me) ;
fyle : TEXT_FILE := TEXT_FILE::open_for_read(fpath.str) ;
if void(fyle)
or fyle.error then -- couldn't find the file?
report.error(File_Access,fpath.str) ;
return void
end ;
me.file_name := fpath ;
me.fyle_cursor := fyle.fstr.str.cursor ;
fyle.close ;
-- The next three lines set up for the pre-processing iter.
me.nesting_valid := true ; -- in case first line is not cond!
me.stack := FLIST{BOOL}::create ;
me.else_seen := FLIST{BOOL}::create ;
me.toggles := FSET{STR}::create ;
me.escape_char := void ; -- ie there isn't one!
me.line_no := 1 ;
return me
end ;
create(
fyle : TEXT_FILE,
name : FILE_PATH
) : SAME
pre ~void(name)
post true
is
-- This routine is provided for use where no pre-processing is to be
-- done and checking/opening of file has external requirements. The file
-- is closed before exit!
me : SAME := new ;
-- The next three lines set up for the pre-processing iter.
me.file_name := name ;
me.fyle_cursor := fyle.fstr.str.cursor ;
fyle.close ;
me.nesting_valid := true ; -- in case first line is not cond!
me.stack := FLIST{BOOL}::create ;
me.else_seen := FLIST{BOOL}::create ;
me.toggles := FSET{STR}::create ;
me.escape_char := void ; -- ie there isn't one!
me.line_no := 1 ;
return me
end ;
comment : STR is
-- This returns the start of comment string which is currently being
-- used.
return fyle_cursor.comment_start
end ;
comment(
chars : STR
)
pre (chars.size > 0)
post true
is
-- This routine establishes the first two characters of chars as the
-- current comment characters for this source text.
if chars.size = 1 then -- only a single character
fyle_cursor.set_comment_start(chars[0])
else
fyle_cursor.set_comment_start(chars)
end
end ;
escape : CHAR
pre ~void(self)
post result = escape_char
is
-- This routine returns the current escape character for this source.
return escape_char
end ;
escape(
ch : CHAR
)
pre ~void(self)
post escape_char = ch
is
-- This routine establishes ch as the escape character for the source
-- until a further change takes place.
escape_char := ch
end ;
finished : BOOL is
-- This predicate returns true if and only if the file cursor has
-- reached the end of the file.
return void(self) -- which means it 'has'
or fyle_cursor.is_done
end ;
private line_escaped(
str : FSTR
) : BOOL is
-- This predicate returns true if and only if the (possible) sequence of
-- escape characters at the end of the line has an odd count - meaning that
-- the end of the line is escaped.
cnt : CARD := 0 ;
loop
index : CARD := (str.loc - 1).downto!(0) ;
if str[index] = escape then
cnt := cnt + 1
else
break!
end
end ;
return cnt.is_odd
end ;
line! : STR_CURSOR
pre ~void(self)
post ~void(result) -- or has quit!
is
-- This iter assembles one or more source lines into a logical line
-- without conditional pre-processing, omitting comment lines and stripping
-- unwanted line marks at the end as necessary.
text : FSTR := FSTR::create ;
escaped : BOOL := false ;
loop
if fyle_cursor.is_done then
if escaped
and (text.size > 0) then -- escaped at end?
res : STR_CURSOR := text.str.cursor ;
res.set_comment_start(fyle_cursor.comment_start) ;
yield res
else
quit
end ;
quit
end ;
tmp_str : STR := fyle_cursor.get_str.strip ;
if tmp_str.size > 0 then
loc_cursor : STR_CURSOR := tmp_str.cursor ;
loc_cursor.set_comment_start(fyle_cursor.comment_start) ;
loc_cursor.skip_space ; -- and comments too!
if ~loc_cursor.is_done then -- something there!
loc_str : FSTR := FSTR::create(loc_cursor.get_str) ;
if ~escaped then
if fyle_cursor.is_done then
line_no := fyle_cursor.line_no
else
line_no := fyle_cursor.line_no - 1
end
end ;
if (loc_str[loc_str.loc - 1] = escape) then
-- need to check if line escaped!
escaped := line_escaped(loc_str) ;
if escaped then
loc_str.loc := loc_str.loc - 1
end
else
escaped := false
end ;
text := text + loc_str ;
if ~escaped then
res : STR_CURSOR := text.str.cursor ;
res.set_comment_start(fyle_cursor.comment_start) ;
-- an inheritance!
yield res ;
text := FSTR::create
end
end
end
end
end ;
pp_line! : STR_CURSOR
pre ~void(self)
post ~void(result) -- or has quit!
is
-- This iter loops through source lines until one which has data is
-- found. Any end of line mark is then stripped. If the last character is
-- an escape character then the 'line' is continued on the next source line.
-- Leading spaces are then omitted in yielding the resulting valid text line!
valid_line : FSTR ;
escaped : BOOL := false ;
toggle_name : STR ;
text : FSTR := FSTR::create ;
loop
if fyle_cursor.is_done then
if ~(stack.size = 0) then
report.line_error(Bad_Nesting,(fyle_cursor.line_no - 1))
end ;
if escaped then -- escaped at end?
res : STR_CURSOR := text.str.cursor ;
res.set_comment_start(fyle_cursor.comment_start) ;
yield res
end ;
quit
end ;
valid_line := FSTR::create(fyle_cursor.get_str) ;
if ((void(valid_line) or (valid_line.size = 0))
and fyle_cursor.is_done) then
quit
end ;
#OUT+"source.sa pp_line! valid_line:"+valid_line.str+"\n";
loc_cursor : STR_CURSOR := valid_line.str.cursor ;
loc_cursor.set_comment_start(fyle_cursor.comment_start) ;
loc_cursor.skip_space ;
key_word : STR;
if loc_cursor.is_done then
#OUT+"source.sa pp_line! loc_cursor.is_done\n";
else
key_word := loc_cursor.get_word ;
loc_cursor.skip_space ;
if ~loc_cursor.is_done then
toggle_name := loc_cursor.get_word ;
else
toggle_name := void
end ;
key : CONDITIONALS := CONDITIONALS::create(key_word) ;
case key
when CONDITIONALS::Define then
if void(toggle_name) then
report.line_error(Missing_Toggle,(fyle_cursor.line_no - 1))
elsif toggles.test(toggle_name) then
report.line_error(Dup_Name,(fyle_cursor.line_no - 1), toggle_name)
else
toggles := toggles.insert(toggle_name)
end
when CONDITIONALS::Undefine then
if void(toggle_name) then
report.line_error(Missing_Toggle,(fyle_cursor.line_no - 1))
elsif toggles.test(toggle_name) then
toggles := toggles.delete(toggle_name)
else
report.line_error(Unknown,
(fyle_cursor.line_no - 1),toggle_name)
end
when CONDITIONALS::If_Defined then
stack := stack.push(nesting_valid) ;
if nesting_valid then
if void(toggle_name) then
report.line_error(Missing_Toggle,
(fyle_cursor.line_no - 1))
else
nesting_valid := toggles.test(toggle_name)
end ;
stack := stack.push(~nesting_valid)
else
stack := stack.push(false)
end ;
else_seen := else_seen.push(false) ;
when CONDITIONALS::If_Not_Defined then
stack := stack.push(nesting_valid) ;
if nesting_valid then
if void(toggle_name) then
report.line_error(Missing_Toggle,
(fyle_cursor.line_no - 1))
else
nesting_valid := ~toggles.test(toggle_name)
end ;
stack := stack.push(~nesting_valid)
else
stack := stack.push(false)
end ;
else_seen := else_seen.push(false) ;
when CONDITIONALS::Else_If then
if (stack.size = 0) then
report.line_error(Bad_Nesting,(fyle_cursor.line_no - 1))
else
nesting_valid := stack.pop ;
if nesting_valid then
if void(toggle_name) then
report.line_error(Missing_Toggle,
(fyle_cursor.line_no - 1))
else
nesting_valid := toggles.test(toggle_name)
end ;
stack := stack.push(~nesting_valid)
else
stack := stack.push(false)
end
end
when CONDITIONALS::Else then
if (stack.size = 0) then
report.line_error(Bad_Nesting,(fyle_cursor.line_no - 1))
else
nesting_valid := stack.pop ;
else_seen := else_seen.push(~else_seen.pop)
end
when CONDITIONALS::End_If then
if (stack.size = 0) then
report.line_error(Bad_Nesting,(fyle_cursor.line_no - 1))
else
if ~else_seen.top then -- need to get off the else value!
nesting_valid := stack.pop
end ;
dummy : BOOL := else_seen.pop ;
nesting_valid := stack.pop
end
else -- NOT a pre-processing directive
if nesting_valid then
if ~loc_cursor.skip_comment then
-- not a comment!
valid_line := valid_line.strip ; -- line marks
if valid_line.size > 0 then -- something there!
if ~escaped then
if fyle_cursor.is_done then
line_no := fyle_cursor.line_no
else
line_no := fyle_cursor.line_no - 1
end
end ;
if (valid_line[valid_line.loc - 1] = escape) then
-- need to check if line escaped!
escaped := line_escaped(valid_line) ;
if escaped then
valid_line.loc := valid_line.loc - 1
end
else
escaped := false
end ;
text := text + valid_line ;
if ~escaped then
res : STR_CURSOR := text.str.cursor ;
res.set_comment_start(
fyle_cursor.comment_start) ;
-- an inheritance!
yield res ;
text := FSTR::create
end
end
end
end
end
end
end ;
end;
pp_line : STR_CURSOR
pre ~void(self)
post true
is
-- This is merely a single line version of the iter for 'prefix type'
-- testing, etc.
loop
return pp_line!
end ;
return void
end ;
line : STR_CURSOR
pre ~void(self)
post true
is
-- This is merely a single line version of the iter for 'prefix type'
-- testing, etc.
loop
return line!
end ;
return void
end ;
end ; -- SOURCE