## libfile-parser-g.pkg -- Toplevel interpreter for .lib file syntax.
# Compiled by:
#
src/app/makelib/makelib.sublib# This is the Mythryl .lib-file parser.
#
# Our primary input is the filename
# of a root .lib file.
#
# We parse that .lib file and all
# .lib files recursively reachable
# from it, then return a dependency
# graph with a node for each .lib file
# and an edge for each reference of
# a .lib file from another one.
#
# Our dependency graph is defined in:
#
#
src/app/makelib/depend/inter-library-dependency-graph.pkg#
#
#
###################################################
# If our freeze_policy argument is FREEZE_ALL,
# which it is when we are called from
#
#
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg#
# then we recursively build .frozen freezefiles for
# all real libraries, which of course requires first
# compiling all their .api and .pkg sourcefiles down
# to .api.compiled and .pkg.compiled object-code files.
#
# In this case the actual compiles take place as a
# side-effect of our
#
# freezefile::save_freezefile ()
#
# calls -- see
#
#
src/app/makelib/freezefile/freezefile-g.pkg#
#
#
###################################################
# If our freeze_policy argument is FREEZE_NONE,
# which it is when we are called from the
#
#
src/app/makelib/main/makelib-g.pkg#
# files, then in general all compiling of .api and
# .pkg files down to .compiled files is done later via:
#
#
src/app/makelib/compile/compile-in-dependency-order-g.pkg#
# Both the standard and the bootstrap compiler
# call us to build their inter-library dependency
# graphs.
#
# E.g., our
#
# libfile_parser_g
#
# generic is invoked from both
#
#
src/app/makelib/main/makelib-g.pkg#
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg#
# at compiletime, and both also invoke our
#
# parse_libfile_tree_and_return_interlibrary_dependency_graph()
#
# entrypoint at runtime.
# inter_library_dependency_graph is from
src/app/makelib/depend/inter-library-dependency-graph.pkg # anchor_dictionary is from
src/app/makelib/paths/anchor-dictionary.pkg # library_source_index is from
src/app/makelib/stuff/library-source-index.pkg # makelib_state is from
src/app/makelib/main/makelib-state.pkg # libfile_grammar_actions is from
src/app/makelib/parse/libfile-grammar-actions.pkg # symbol_map is from
src/app/makelib/stuff/symbol-map.pkg # inter_library_dependency_graph is from
src/app/makelib/depend/inter-library-dependency-graph.pkgstipulate
package ad = anchor_dictionary; # anchor_dictionary is from
src/app/makelib/paths/anchor-dictionary.pkg package err = error_message; # error_message is from
src/lib/compiler/front/basics/errormsg/error-message.pkg package fil = file__premicrothread; # file__premicrothread is from
src/lib/std/src/posix/file--premicrothread.pkg package fp = filename_policy; # filename_policy is from
src/app/makelib/main/filename-policy.pkg package frn = find_reachable_sml_nodes; # find_reachable_sml_nodes is from
src/app/makelib/depend/find-reachable-sml-nodes.pkg package fzp = freeze_policy; # freeze_policy is from
src/app/makelib/parse/freeze-policy.pkg package lg = inter_library_dependency_graph; # inter_library_dependency_graph is from
src/app/makelib/depend/inter-library-dependency-graph.pkg package lnd = line_number_db; # line_number_db is from
src/lib/compiler/front/basics/source/line-number-db.pkg package log = logger; # logger is from
src/lib/src/lib/thread-kit/src/lib/logger.pkg package mld = makelib_defaults; # makelib_defaults is from
src/app/makelib/stuff/makelib-defaults.pkg package ms = makelib_state; # makelib_state is from
src/app/makelib/main/makelib-state.pkg package pp = standard_prettyprinter; # standard_prettyprinter is from
src/lib/prettyprint/big/src/standard-prettyprinter.pkg package ri = runtime_internals; # runtime_internals is from
src/lib/std/src/nj/runtime-internals.pkg package sci = sourcecode_info; # sourcecode_info is from
src/lib/compiler/front/basics/source/sourcecode-info.pkg package sg = intra_library_dependency_graph; # intra_library_dependency_graph is from
src/app/makelib/depend/intra-library-dependency-graph.pkg package spm = source_path_map; # source_path_map is from
src/app/makelib/paths/source-path-map.pkg package sps = source_path_set; # source_path_set is from
src/app/makelib/paths/source-path-set.pkg package tlt = thawedlib_tome; # thawedlib_tome is from
src/app/makelib/compilable/thawedlib-tome.pkg package ts = timestamp; # timestamp is from
src/app/makelib/paths/timestamp.pkg Pp = pp::Pp;
# Logging support. To log messages from this file scatter
#
# to_log {. sprintf "Whatever"; }; # Do not add trailing newline to message string.
#
# calls through the code as appropriate and then either
# uncomment the below
#
# my _ = log::enable libfile_parser_logging;
#
# line or do
#
# logger::enable (the (logger::find_logtree_node_by_name "libfile_parser::logging"));
#
# from the Mythryl interactive prompt.
#
libfile_parser_logging
=
log::make_logtree_leaf
{ parent => fil::all_logging,
name => "libfile_parser::logging",
default => FALSE # Change to TRUE or call (log::enable libfile_parser_logging) to enable logging in this file.
};
#
to_log = log::log_if libfile_parser_logging 0;
herein
generic package libfile_parser_g (
#
drop_stale_entries_from_compiler_and_linker_maps: Void -> Void;
#
package freezefile_roster: Freezefile_Roster; # Freezefile_Roster is from
src/app/makelib/freezefile/freezefile-roster-g.pkg package freezefile: Freezefile; # Freezefile is from
src/app/makelib/freezefile/freezefile.api )
: Libfile_Parser # Libfile_Parser is from
src/app/makelib/parse/libfile-parser.api {
stipulate
package ffr = freezefile_roster;
package fzf = freezefile;
package vff
=
verify_freezefile_g (package freezefile = freezefile;);
lookahead = 30;
# lr_parser is from
src/app/yacc/lib/parser2.pkg # make_complete_yacc_parser_with_custom_argument_g is from
src/app/yacc/lib/make-complete-yacc-parser-with-custom-argument-g.pkg package libfile_lr_vals = libfile_lr_vals_fun (package token = lr_parser::token;);
package libfilelex = makelib_lex_g (package tokens = libfile_lr_vals::tokens;);
package libfile_parser
=
make_complete_yacc_parser_with_custom_argument_g (
#
package parser_data = libfile_lr_vals::parser_data;
package lex = libfilelex;
package lr_parser = lr_parser;
);
freezefile_cache
=
REF (spm::empty: spm::Map( lg::Library ));
#
#
fun do_major_cleaning ()
=
ri::hc::clean_heap 7;
#
fun get_freezefile_cache_entry (makefile_path, primordial_library as lg::LIBRARY { libfile, ... } )
=>
if (ad::compare (makefile_path, libfile) == EQUAL)
#
THE primordial_library;
else
spm::get (*freezefile_cache, makefile_path);
fi;
get_freezefile_cache_entry (_, lg::BAD_LIBRARY)
=>
NULL;
end;
# When an entry A vanishes from the freezefile cache
# (this only happens in paranoid mode), then all
# the other ones that refer to A must vanish, too.
#
# They might still be valid themselves,
# but if they had been unpickled before
# A became invalid they will point to
# invalid data.
#
# By removing them from the cache we force
# them to be re-read and re-unpickled.
# This restores sanity.
#
fun delete_cached_freezefile
(
makelib_state: ms::Makelib_State,
p,
version, # XXX BUGGO FIXME 'version' here can die, I think.
lg::LIBRARY { libfile => igp, ... }
)
=>
{ changed = REF TRUE;
policy = makelib_state.makelib_session.filename_policy;
freezefile_name
=
fp::make_freezefile_name
policy
p;
# filename_policy is from
src/app/makelib/main/filename-policy.pkg #
fun can_stay lg::BAD_LIBRARY
=>
TRUE; # Doesn't matter
can_stay (lg::LIBRARY { sublibraries, ... } )
=>
canstay
where
fun good_sublib (lt: lg::Library_Thunk)
=
case (lt.library_thunk ())
#
lg::LIBRARY { more => lg::MAIN_LIBRARY { frozen_vs_thawed_stuff => lg::FROZENLIB_STUFF _, ... }, ... }
=>
ad::compare (p, igp) == EQUAL
or
spm::contains_key (*freezefile_cache, p);
#
_ => TRUE;
esac;
canstay = list::all good_sublib sublibraries;
if (not canstay)
#
changed := TRUE;
fi;
end;
end;
# Logically remove the freezefile from the registry:
#
freezefile_cache := spm::drop (*freezefile_cache, p);
# Physically remove the freezefile:
#
winix__premicrothread::file::remove_file freezefile_name
except
_ = ();
# Restore sanity in the registry:
#
for (*changed) {
changed := FALSE;
freezefile_cache
:=
spm::filter can_stay *freezefile_cache;
};
};
delete_cached_freezefile (_, _, _, lg::BAD_LIBRARY)
=>
();
end;
# anchor_dictionary is from
src/app/makelib/paths/anchor-dictionary.pkg # source_path_map is from
src/app/makelib/paths/source-path-map.pkg # library_source_index is from
src/app/makelib/stuff/library-source-index.pkg # Recursively parse the tree of .lib files
# rooted at makelib_file_to_read, returning their
# contents in the form of a dependency graph
# with one node per .lib file.
#
# NB: Technically, the .lib "tree" is in
# fact a dag ("directed acyclic graph"),
# since a given .lib file may have many
# parents as well as many children.
# Which is to say, many libraries may
# use a given low-level library file.
#
fun parse_libfile_tree_and_return_interlibrary_dependency_graph { # This function is part of our exported interface.
#
makelib_file_to_read: ad::File, # Our primary input, foo.lib or such.
load_plugin, # Function to load a plugin given its directory and filename.
library_source_index, # Maps makelib filenames to instances of sourcecode_info::Input_Source.
makelib_session, # Holds 'filename_policy', 'keep_going_after_compile_errors', 'server_mode' ...
freeze_policy, # See explanation in
src/app/makelib/parse/freeze-policy.api primordial_library, # Preparsed copy of src/lib/core/init/init.cmi since we can't parse it ourself (uses different syntax).
paranoid # TRUE to do extra validity checking.
}
=
{
ad::sync ();
# Write per-makefile .compile.log files.
# We don't currently put anything interesting
# in them, but just creating them distinguishes
# live .lib files from deadwood:
#
makefile_filename # Something like "/pub/home/.../root.lib"
=
ad::os_string makelib_file_to_read;
unparse_filename # Construct "/pub/home/.../root.lib.compile.log"
=
makefile_filename + ".compile.log";
pp = standard_prettyprinter::make_standard_prettyprinter_into_file unparse_filename [];
pp.lit "This logfile generated by src/app/makelib/parse/parse-makelib-g.pkg\n";
pp.flush ();
pp.close ();
primordial_library_path
=
case primordial_library
#
lg::LIBRARY x => x.libfile;
lg::BAD_LIBRARY => err::impossible "parse-makelib-g.pkg: parse: bad primordial_library value";
esac;
freeze_this_library = freeze_policy != fzp::FREEZE_NONE;
freeze_all_libraries = freeze_policy == fzp::FREEZE_ALL;
library_source_index = library_source_index;
plaint_sink = err::default_plaint_sink ();
timestamp_of_youngest_sourcefile_in_library = REF ts::ancient; # Used to decide whether a library rebuild is needed, in
src/app/makelib/main/makelib-g.pkg #
fun libname library
=
the_else
(
null_or::map ad::describe library,
"<toplevel>"
);
makelib_state0
=
{ makelib_session,
library_source_index,
#
plaint_sink,
timestamp_of_youngest_sourcefile_in_library
};
keep_going_after_compile_errors
=
makelib_session.keep_going_after_compile_errors;
# null_or is from
src/lib/std/src/null-or.pkg # frozenlib_tome_map is from
src/app/makelib/freezefile/frozenlib-tome-map.pkg # freeze_policy is from
src/app/makelib/parse/freeze-policy.pkg # source_path_map is from
src/app/makelib/paths/source-path-map.pkg # The libfile cache saves the results of
# processing .lib files, for possible
# re-use later.
#
# Having NULL registered for a .lib file means
# that a previous attempt to run it failed.
#
# We initialize it with a parsed copy of the
# primordial library because it uses special
# special that this file's logic will choke on:
#
libfile_cache
=
REF (spm::singleton
( primordial_library_path, # "src/lib/core/init/init.cmi"
THE primordial_library # Parsed version of init.cmi.
)
);
exports_map
=
REF frozenlib_tome_map::empty;
#
fun update_exports_map # Called only in paranoid mode after verifying ns_g (producing s_g).
( lg::LIBRARY ns_g,
lg::LIBRARY s_g
)
=>
{ s_e = s_g.catalog;
#
fun add (symbol, t: lg::Fat_Tome)
=
case (t.masked_tome_thunk ()) # src
#
{ exports_mask,
tome_tin => sg::TOME_IN_THAWEDLIB (sg::THAWEDLIB_TOME_TIN sourcefile_tin)
}
=>
case (symbol_map::get (s_e, symbol))
#
NULL => ();
#
THE (t: lg::Fat_Tome)
=>
case (t.masked_tome_thunk ())
#
{ exports_mask,
tome_tin => sg::TOME_IN_FROZENLIB { frozenlib_tome_tin => sg::FROZENLIB_TOME_TIN freezefile_tin, ... }
}
=>
exports_map
:=
frozenlib_tome_map::set (
*exports_map,
freezefile_tin.frozenlib_tome,
sourcefile_tin.thawedlib_tome
);
_ => ();
esac;
esac;
_ => ();
esac;
symbol_map::keyed_apply
add
ns_g.catalog;
};
update_exports_map _
=>
();
end;
#
fun register_new_freezefile (gpath, g)
=
{ freezefile_cache
:=
spm::set (*freezefile_cache, gpath, g);
sps::apply
(tlt::clean_library TRUE)
(frn::groups_of g);
drop_stale_entries_from_compiler_and_linker_maps ();
libfile_cache := spm::drop (*libfile_cache, gpath);
do_major_cleaning (); # For good measure.
};
#
fun has_cycle (root_library, library_stack)
=
{ # Check for cycles among libraries and print them nicely:
#
fun find_cycle ([], _)
=>
[];
find_cycle ((h as (this_library, (s, p1, p2))) ! t, cycle)
=>
if (ad::compare (this_library, root_library) == EQUAL)
#
reverse (h ! cycle);
else
find_cycle (t, h ! cycle);
fi;
end;
#
fun report ((this_library, (s, p1, p2)), hist)
=
{ fun pphist (pp:Pp)
=
loop (this_library, hist)
where
fun loop (_, [])
=>
();
loop (g0, (this_library, (s, p1, p2)) ! t)
=>
{ s = err::match_error_string s (p1, p2);
pp.newline();
pp.lit s;
pp.lit ": importing ";
pp.lit (ad::describe g0);
loop (this_library, t);
};
end;
end;
err::error s (p1, p2) err::ERROR
("library hierarchy forms a cycle with " +
ad::describe root_library)
pphist;
};
case (find_cycle (library_stack, []))
h ! t
=>
{ report (h, t);
TRUE;
};
[] =>
FALSE;
esac;
};
#
fun main_parse
(
makelib_file_to_read,
version,
library_stack,
p_err_flag,
freeze_this_library,
this_library,
makelib_state,
anchor_renamings, # MUSTDIE
error
)
=
{
fun load_freezefile_from_disk
freezefile_stack
( makelib_state,
makefile_path,
version # XXX BUGGO DELETEME
, anchor_renamings # MUSTDIE
)
=
{ # Detect cycles among freezefiles.
#
# Such cycles should never occur
# unless someone purposely renames
# freezefiles in a bad way:
fun find_cycle ([], _)
=>
NULL;
find_cycle (h ! t, cycle)
=>
if (ad::compare (h, makefile_path) == EQUAL)
THE (h ! cycle);
else find_cycle (t, h ! cycle);
fi;
end;
#
fun report cycle
=
{ fun pphist (pp:Pp)
=
loop (reverse cycle)
where
fun loop []
=>
();
loop (h ! t)
=>
{ pp.newline();
pp.lit (ad::describe h);
loop t;
};
end;
end;
err::error_no_file
(plaint_sink, p_err_flag)
lnd::null_region
err::ERROR
("freezefiles form a cycle with " + ad::describe makefile_path)
pphist;
};
#
fun load_freezefile ()
=
{
maybe_afreezefile
=
fzf::load_freezefile
{
get_library => load_freezefile_from_disk (makefile_path ! freezefile_stack),
saw_errors => p_err_flag
}
( makelib_state,
makefile_path,
version # XXX BUGGO DELETEME
, anchor_renamings # MUSTDIE
);
case maybe_afreezefile
NULL
=>
{
NULL;
};
THE freezefile
=>
{
register_new_freezefile (makefile_path, freezefile);
name1 = ad::describe makefile_path;
name2 = ad::abbreviate (ad::os_string' makefile_path);
# Narrate both names to console only if
# the second adds significant information:
#
if (string::is_suffix name2 name1) fil::say {. cat [ "\n libfile-parser-g.pkg: Library ", name1, " is up to date."]; };
else fil::say {. cat [ "\n libfile-parser-g.pkg: Library ", name1, " (", name2, ") is up to date."]; };
fi;
THE freezefile;
};
esac;
};
case (find_cycle (freezefile_stack, []))
#
THE cycle
=>
{ report cycle;
NULL;
};
#
NULL
=>
case (get_freezefile_cache_entry (makefile_path, primordial_library))
#
THE freezefile => {
THE freezefile;
};
NULL => {
load_freezefile ();
};
esac;
esac;
}; # fun load_freezefile_from_disk
#
fun freeze_library (NULL
, _ # MUSTDIE
)
=>
NULL;
freeze_library (THE library
, anchor_renamings # MUSTDIE
)
=>
# We may not freeze sublibraries,
# only main ones, so start by checking
# its 'more' field to see which we have:
#
case library
#
lg::BAD_LIBRARY => NULL;
#
lg::LIBRARY { more => lg::MAIN_LIBRARY _, ... }
=>
{
frozen_library
=
fzf::save_freezefile makelib_state
{
library,
saw_errors => p_err_flag
,renamings => anchor_renamings # MUSTDIE
};
case frozen_library
#
THE library'
=>
{ register_new_freezefile (makelib_file_to_read, library');
#
THE library';
};
NULL => NULL;
esac;
};
lg::LIBRARY { more => lg::SUBLIBRARY _, libfile, ... }
=>
THE library;
esac;
end; # fun freeze_library
# (resuming: fun main_parse)
# A VIRTUAL library must be a member of
# exactly one REAL library. Check this:
#
case (spm::get (*libfile_cache, makelib_file_to_read))
#
THE library_or_null
=>
library_or_null
where
case library_or_null
#
THE (lg::LIBRARY { more => lg::SUBLIBRARY { main_library, ... }, ... } )
=>
{
fun eq (NULL, NULL) => TRUE;
eq (THE p, THE p') => ad::compare (p, p') == EQUAL;
eq _ => FALSE;
end;
if (not (eq (this_library, main_library)))
#
error (cat ["library ",
ad::describe makelib_file_to_read,
" appears as member of \
\two different libraries: ",
libname main_library, " and ",
libname this_library, "\n"]);
#
p_err_flag := TRUE;
fi;
};
_ => ();
esac;
end;
NULL
=>
{ fun find_and_load_freezefile ()
=
load_freezefile_from_disk [] (makelib_state, makelib_file_to_read, version
, anchor_renamings # MUSTDIE
);
#
fun find_and_parse_libfile ()
=
parse' (makelib_file_to_read, library_stack, p_err_flag, this_library, makelib_state
, anchor_renamings # MUSTDIE
);
#
fun add_makefile_to_cache (library_or_null: Null_Or(lg::Library))
=
{ libfile_cache
:=
spm::set
(
*libfile_cache,
makelib_file_to_read,
library_or_null
);
library_or_null;
};
# source_path_map is from
src/app/makelib/paths/source-path-map.pkg #
fun cache_and_maybe_freeze_library library_or_null
=
add_makefile_to_cache (
if freeze_this_library
#
freeze_library (library_or_null
, anchor_renamings # MUSTDIE
);
else
tlt::clean_library FALSE makelib_file_to_read;
library_or_null;
fi
);
if (not paranoid)
#
case (find_and_load_freezefile ())
#
THE lib => add_makefile_to_cache (THE lib);
NULL => cache_and_maybe_freeze_library (find_and_parse_libfile ());
esac;
else
case (find_and_parse_libfile ())
#
NULL => add_makefile_to_cache NULL;
#
THE lib
=>
{ library_or_null'
=
if (vff::verify makelib_state *exports_map lib)
#
add_makefile_to_cache (
#
case (find_and_load_freezefile ())
#
THE lib' => THE lib';
NULL => THE lib;
esac
);
else
delete_cached_freezefile (makelib_state, makelib_file_to_read, version, primordial_library);
cache_and_maybe_freeze_library (THE lib);
fi;
case library_or_null'
#
THE lib'
=>
{ update_exports_map (lib, lib');
THE lib';
};
#
NULL => NULL;
esac;
};
esac;
fi;
};
esac;
} # fun main_parse
# Parse' is used when we are sure
# that we don't want to load a
# freezefile:
#
also
fun parse' (makelib_file_to_read, library_stack, p_err_flag, this_library, makelib_state
, anchor_renamings # MUSTDIE XXX BUGGO FIXME should kill anchor_renamings arg.
)
=
{ # Normal processing -- used when
# there is no cycle to report:
#
fun normal_processing ()
=
{ fil::say {.
cat [
" libfile-parser-g.pkg: Reading library file ",
(number_string::pad_right ' ' 50 (ad::abbreviate (ad::os_string' makelib_file_to_read))),
"\ton behalf of ",
libname this_library
];
};
path_root = ad::dir makelib_file_to_read;
local_index = libfile_grammar_actions::make_tool_index ();
#
safely::do
{
open_it => {. fil::open_for_read (ad::os_string makelib_file_to_read); },
close_it => fil::close_input,
cleanup => \\ _ = ()
}
{. source = sci::make_sourcecode_info
{
file_name => ad::abbreviate (ad::os_string makelib_file_to_read),
line_num => 1,
source_stream => #stream,
is_interactive => FALSE,
error_consumer => plaint_sink
};
line_number_db
=
source.line_number_db;
library_source_index::register
library_source_index
(makelib_file_to_read, source);
# We can hard-wire the source into this
# error function because the function
# is only for immediate use and doesn't
# get stored into persistent data structures:
#
fun report_error source_region message
=
err::error source source_region err::ERROR message err::null_error_body;
#
fun complain_about_obsolete_syntax r
=
if (mld::warn_on_obsolete_syntax.get ())
#
err::error source r err::WARNING "old-style feature (obsolete)" err::null_error_body;
fi;
# Return value of 'recursive_parse' is a library (never NULL).
# This function is used to parse sub-libraries.
# Errors are propagated by explicitly setting the
# "saw_errors" flag of the parent library:
#
fun recursive_parse
(src_pos1, src_pos2)
this_library
(makelib_file_to_parse,
version # XXX BUGGO DELEME 'version' should die
, anchor_renamings # MUSTDIE
)
=
{
library_stack'
=
(makelib_file_to_read, (source, src_pos1, src_pos2))
!
library_stack;
saw_errors = source.saw_errors;
# Unless we are in keep-going mode we do no further
# recursive dagwalks once there was an error on
# this makefile:
#
if (*saw_errors and not keep_going_after_compile_errors)
#
lg::BAD_LIBRARY;
else
case (main_parse (
makelib_file_to_parse, # New .lib file to run recursively.
version,
library_stack',
saw_errors,
freeze_all_libraries,
this_library,
makelib_state,
anchor_renamings, # MUSTDIE
report_error (src_pos1, src_pos2)
))
THE result => {
result;
};
NULL => { saw_errors := TRUE;
lg::BAD_LIBRARY;
};
esac;
fi;
}
except
exn as io_exceptions::IO _
=
{ report_error
(src_pos1, src_pos2)
(exceptions::exception_message exn);
lg::BAD_LIBRARY;
};
# libfile_grammar_actions is from
src/app/makelib/parse/libfile-grammar-actions.pkg #
fun make_member
(
{ name, make_path },
src_pos1, src_pos2,
ilk,
tool_options
)
=
libfile_grammar_actions::make_member
{ makelib_state,
load_plugin,
recursive_parse => recursive_parse (src_pos1, src_pos2)
}
{ name,
make_path,
ilk,
tool_options,
library => (makelib_file_to_read, (src_pos1, src_pos2)),
local_index,
path_root
};
# Build the Lex_Arg argument for
# the lexer as defined in
#
# src/app/makelib/parse/libfile.lex
#
# The lexer's local state is
# encapsulated here to make sure
# the parser is re-entrant:
#
lexarg
=
{ # Local state:
depth = REF 0;
curstring = REF [];
startpos = REF 0;
instring = REF FALSE;
####################################
# Comment handling -- mostly tracking comment nesting depth:
fun enter_comment ()
=
depth := *depth + 1;
#
fun leave_comment ()
=
{ d = *depth - 1;
depth := d;
d == 0;
};
# Handling double-quoted string literals:
#
fun enter_qquote pos
=
{ instring := TRUE;
curstring := [];
startpos := pos;
};
#
fun append_char_to_qquote (c: Char)
=
curstring := c ! *curstring;
fun append_control_char_to_qquote (yytext: String, base_char: Char)
=
# We're adding a control character to current qquote (double-quoted string literal)
# based on a spec like "^A" or "^a" (i.e., a carat followed by an alphabetic).
#
# To do this we need to read the char in question out of yytext and then subtract
# off base_char (either 'A' or 'a') to convert from alphabetic to control-char range.
#
append_char_to_qquote (char::from_int (string::get_byte (yytext, 2) - (char::to_int base_char)));
#
fun append_escaped_char_to_qquote (string: String, source_position: Int)
=
# We're adding to the current qquote (double-quoted string literal)
# a char encoded as a three-char octal escape like \015:
#
{ ns = substring (string, 1, 3); # Skip the \, get the 015 part.
#
n = (string::get_byte(ns,0)-(char::to_int '0'))*64 # Convert the 015 (or whatever) from ascii to an integer.
+ (string::get_byte(ns,1)-(char::to_int '0'))*8
+ (string::get_byte(ns,2)-(char::to_int '0'));
append_char_to_qquote (char::from_int n) # Convert the integer to a char and append it.
except _
=
report_error
(source_position, source_position + size string)
("illegal octal char spec: " + ns);
};
#
fun leave_qquote
( source_position: Int,
token # : ((String, /*start:*/Int, /*stop:*/Int) -> Lex_Result)
)
=
{ instring := FALSE;
token (implode (reverse *curstring), *startpos, source_position);
};
# Handle end-of-file -- signal error if
# we have an unclosed comment or string:
#
fun handle_eof_by_complaining_about_unclosed_comments_and_strings ()
=
pos
where
pos = lnd::last_change line_number_db;
#
if (*depth > 0) report_error (pos, pos) "unexpected end of input in comment";
elif (*instring) report_error (pos, pos) "unexpected end of input in string";
fi;
end;
#
fun newline pos # Called on each '\n' -- lets us track current line number.
=
lnd::newline line_number_db pos;
#
fun handle_line_directive (p, t) # Handling #line directives
=
{ fun sep c
=
c == '#' or char::is_space c;
#
fun convert s
=
the_else (int::from_string s, 0);
#
fun r (line, col, file)
=
lnd::resynch
line_number_db
(p, { file_name => file,
line, column => col } );
case (string::tokens sep t)
#
[_, line ] => r (convert line, NULL, NULL );
[_, line, file ] => r (convert line, NULL, THE file);
[_, line, col, file ] => r (convert line, THE (convert col), THE file);
_ => report_error (p, p + size t) "illegal #line directive";
esac;
};
{ enter_comment,
leave_comment,
#
enter_qquote,
append_char_to_qquote,
append_control_char_to_qquote,
#
append_escaped_char_to_qquote,
leave_qquote,
#
handle_eof_by_complaining_about_unclosed_comments_and_strings,
newline,
complain_about_obsolete_syntax,
#
report_error,
handle_line_directive,
#
in_section2 => REF FALSE # Starts FALSE; we set it TRUE once we've seen LIBRARY_COMPONENTS or SUBLIBRARY_COMPONENTS token.
};
};
#
fun inputc k
=
fil::read #stream;
token_stream
=
libfile_parser::make_lexer inputc lexarg;
parsearg # libfile parser argument as defined by %arg in src/app/makelib/parse/libfile.grammar
=
{ libfile => makelib_file_to_read,
path_root,
complain_about_obsolete_syntax,
#
report_error,
make_member,
this_library,
#
makelib_state,
primordial_library
};
my (parse_result, _)
=
libfile_parser::parse (
lookahead,
token_stream,
\\ (message, position1, position2) = report_error (position1, position2) message,
parsearg
);
if *source.saw_errors NULL;
else THE parse_result;
fi;
};
#
}
except
lr_parser::PARSE_ERROR = NULL;
if (has_cycle (makelib_file_to_read, library_stack)) NULL;
else normal_processing ();
fi;
}; # fun parse'
tlt::new_generation ();
case (main_parse (
makelib_file_to_read,
NULL, # version
[], # library_stack
REF FALSE, # p_err_flag
freeze_this_library,
NULL, # this_library
makelib_state0,
[], # anchor_renamings # MUSTDIE
\\ _ = ()
))
#
THE (library as (lg::LIBRARY { more => lg::MAIN_LIBRARY _, ... })) # Normal successful-return case.
=>
THE (library, makelib_state0);
#
NULL => NULL;
#
THE lg::BAD_LIBRARY => NULL; # Not sure if this can actualy happen. -- 2011-10-13 CrT
#
THE (lg::LIBRARY { more => lg::SUBLIBRARY _, libfile, ... })
=>
{ fil::say {.
cat [
"Error: .lib-file tree root ",
(number_string::pad_right ' ' 50 (ad::abbreviate (ad::os_string' libfile))),
" is a sublibrary, not a main library."
];
};
raise exception err::COMPILE_ERROR; # Not sure if this is the best way to report the error.
};
esac;
}; # fun parse_libfile_tree_and_return_interlibrary_dependency_graph
herein
# Here's the stuff we export:
fun clear_state () = freezefile_cache := spm::empty;
fun list_freezefiles () = map #1 (spm::keyvals_list *freezefile_cache);
# This function will be used to delete
# in-memory picklestrings to conserve memory
# if the mythryld commandline switch
#
# -Ccm.conserve-memory=TRUE # <=== this is probably waaay obsolete syntax
#
# is given.
#
# It is called only from
#
#
src/app/makelib/main/makelib-g.pkg #
fun clear_pickle_cache ()
=
spm::apply
delete
*freezefile_cache
where
fun delete (lg::LIBRARY { more => lg::MAIN_LIBRARY { frozen_vs_thawed_stuff => lg::FROZENLIB_STUFF { clear_pickle_cache }, ... }, ... })
=>
clear_pickle_cache ();
delete _
=> ();
end;
end;
#
fun dismiss_freezefile l
=
{ ffr::clear_state (); # Clear global freezefile roster.
#
freezefile_cache
:=
spm::drop (*freezefile_cache, l);
};
parse_libfile_tree_and_return_interlibrary_dependency_graph =
parse_libfile_tree_and_return_interlibrary_dependency_graph;
end;
};
end;
## (C) 1999 Lucent Technologies, Bell Laboratories
## Author: Matthias Blume (blume@kurims.kyoto-u.ac.jp)
## Subsequent changes by Jeff Prothero Copyright (c) 2010-2015,
## released per terms of SMLNJ-COPYRIGHT.