:- module(source_processor).
:- export source_open / 3.
:- export source_read / 4.
:- export source_close / 2.
:- export struct(source_position(filespec, stream, file, line, offset, remaining_files, included_from, options, created_modules, oldcwd, module, ifdefs, old_compiled_stream)).
:- export struct(source_term(term, vars, annotated)).
:- tool(source_open / 3, source_open / 4).
:- export expand_clause_and_goals / 6.
:- tool(expand_clause_and_goals / 6, expand_clause_and_goals_ / 7).
:- export expand_clause_goals / 5.
:- export meta_attribute_now_later / 3.
:- comment(categories, ["Development Tools"]).
:- comment(summary, "Tools for processing ECLiPSe sources").
:- comment(date, "$Date: 2013/02/14 02:55:20 $").
:- comment(copyright, "Cisco Systems, Inc").
:- comment(author, "Joachim Schimpf, IC-Parc").
:- comment(eg, "\n    % This can be used as a template for source processing code:\n    % a source file is opened,\n    % every term is read and printed,\n    % then the file is closed\n\n    test(File) :-\n\tsource_open(File, [], SP0),\n\t(\n\t    fromto(begin, _, Class, end),\n\t    fromto(SP0, SP1, SP2, SPend)\n\tdo\n\t    source_read(SP1, SP2, Class, SourceTerm),\n\t    SP1 = source_position{file:F,line:L,module:M},\n\t    printf(\"%w %w:%d %w%n\", [M,F,L,Class]),\n\t    arg(term of source_term, SourceTerm, Term),\n\t    writeclause(Term)\n\t),\n\tsource_close(SPend, []).\n    ").
:- comment(struct(source_term), [summary : "A source term with additional information", fields : [term : "A term read from a source file (a clause, fact, etc)", vars : "A list of the term's variables and their names information (or [] if the with_annotations option is in effect)", annotated : "An annotated version of term with source and type information (only if with_annotations option given, otherwise uninstantiated)"], see_also : [readvar / 3, read_annotated / 2]]).
:- comment(struct(source_position), [summary : "Current source position", desc : html("This structure describes a particular position that has been\n    \treached during processing of an ECLiPSe source file. It also describes\n\twhat has to be done when this file is finished. The source_read/4\n\tpredicate reads a term from a given source position and returns\n\tthe new source position after the read."), fields : [filespec : "the original source file argument used in source_open/3", stream : "the Eclipse stream being read", file : "this file's canonical file name", line : "this position's line number (integer)", offset : "this position's byte offset (integer)", remaining_files : "list of files still to be included", included_from : "the source_position from which this file is included, or [] if not included", options : "structure describing option settings", created_modules : "list of modules created so far", oldcwd : "current directory before opening this file", module : "read-module at this source position", ifdefs : "list of atoms (then|else) describing nesting of if-directives", old_compiled_stream : "value of compiled_stream/1 when toplevel file was opened"], see_also : [source_open / 3, source_close / 2, source_read / 4]]).
:- comment(source_close / 2, [summary : "Close an open ECLiPSe source file.", args : ["SourcePos" : "Source position handle", "OptionList" : "List of options, possibly empty"], desc : html("This is used to close an ECLiPSe source file that was\n    previously opened with source_open/3.  It is possible to close\n    before the end of the source is reached. Nesting of included\n    files is properly handled.\n    <P>\n    OptionList can contain the following:\n    <DL>\n    <DT>keep_modules</DT>\n    \t<DD>keep the modules that have been created implicitly during\n\tsource processing (by default they are erased to restore the\n\toriginal state)</DD>\n    </DL>\n    <P>\n    Note that if source_open/3 had been called on an already open stream\n    with a stream(Stream) argument, then Stream will not be closed by\n    source_close/2.\n    "), see_also : [source_open / 3], amode : source_close(+, +)]).
:- comment(source_open / 3, [summary : "Open an ECLiPSe source code file for subsequent processing", args : ["File" : "Name of source file (Atom or string), or term of the form stream(Stream)", "OptionList" : "List of options, possibly empty", "SourcePos" : "Source position handle"], desc : html("This predicates opens an ECLiPSe source file (or prepares and\n    already opened stream) for subsequent reading with source_read/4.\n    Compared to the standard primitives for reading from a file, this\n    takes care of\n    <UL>\n    <LI>nesting of included files\n    <LI>creating and keeping track of modules\n    <LI>syntax settings\n    <LI>comments (optional)\n    <LI>changing the current directory to the opened file's directory\n    <LI>handling of if-elif-else-endif directives\n    </UL>\n    OptionList can contain the following:\n    <DL>\n    <DT>keep_comments</DT>\n\t<DD>treat comments and spacing between source terms as data\n\trather than ignoring it</DD>\n    <DT>include_comment_files</DT>\n\t<DD>interpret the comment(include,Files) directive and include\n\tthe contents of the given files, identical to an include(Files)\n\tdirective. By default, these directives are ignored.</DD>\n    <DT>ignore_conditionals</DT>\n\t<DD>Ignore any special meaning of conditional directives (if/1,\n\telif/1, else/0, endif/0) and just return them as a source term.\n\tThe default is to interpret these directives, including or\n\texcluding corresponding source parts accordingly, while removing\n\tthe directives themselves</DD>\n    <DT>with_annotations</DT>\n\t<DD>return an annotated source term with every source term\n\t(and do not return a separate variable list)</DD>\n    <DT>no_macro_expansion</DT>\n\t<DD>do not expand term macros (e.g. with/2 and of/2)</DD>\n    <DT>minimal_macro_expansion</DT>\n\t<DD>do not expand term macros except in :- directives</DD>\n    <DT>no_clause_expansion</DT>\n\t<DD>do not expand clause macros (e.g. DCGs)</DD>\n    <DT>goal_expansion</DT>\n\t<DD>do inline expansion of goals (only works if clause expansion\n\tis not disabled)</DD>\n    <DT>recreate_modules</DT>\n\t<DD>erase and re-create module when encountering a module directive</DD>\n    </DL>\n    source_open/3 and source_read/4 maintain a 'current source position',\n    which is a structure containing (among others) the following fields:\n    <PRE>\n    :- export struct(source_position(\n\tstream,\t\t\t% Eclipse stream\n\tfile,\t\t\t% canonical file name\n\tline,\t\t\t% integer\n\toffset,\t\t\t% integer\n\tincluded_from,\t\t% source_position or []\n\tmodule,\t\t\t% current source module\n\t...\n    )).\n    </PRE>\n    i.e. information about the module context and the precise location\n    of a source term (e.g. for error messages).\n    <P>\n    If File is of the form stream(Stream), then the predicate expects\n    Stream to be an already opened input stream. Correspondingly, the\n    processed stream will not be closed at the end of source processing\n    (unlike files).\n    <P>\n    "), see_also : [source_close / 2, source_read / 4], amode : source_open(+, +, -)]).
:- comment(source_read / 4, [summary : "Read the next term from an open ECLiPSe source file", args : ["SourcePos" : "Source position handle", "NextPos" : "Source position handle", "Kind" : "kind of source term (atom)", "SourceTerm" : "a source_term structure"], desc : html("This reads the next source term from a source file previously\n    opened with source_open/3. The term at the current source position\n    SourcePos is read, and the next source position is returned for use\n    in subsequent source_read/4 invocations (it is not possible to read\n    twice from the same source position!).\n    <P>\n    The term that has been read is classified into one of the following\n    categories (Kind):\n    <DL>\n    <DT>handled_directive</DT>\n    \t<DD>A directive (a term with functor :-/1) which has already\n\tbeen handled (interpreted by source_read/4). Such directives are:\n\tmodule/1,3, local/1, export/1, reexport/1, use_module/1, lib/1,\n\tpragma/1, include/1, ./2, op/3, meta_attribute/2 and\n\tcomment(include,...)</DD>\n    <DT>directive</DT>\n    \t<DD>A directive (a term with functor :-/1) which has not\n\tbeen handled (ignored by source_read/4)</DD>\n    <DT>query</DT>\n    \t<DD>A query (a term with functor ?-/1)</DD>\n    <DT>clause</DT>\n    \t<DD>Any other structure, list or atom (usually a clause)</DD>\n    <DT>var</DT>\n    \t<DD>A term consisting of only a variable (very likely an error)</DD>\n    <DT>other</DT>\n    \t<DD>A number, string, etc (very likely an error)</DD>\n    <DT>comment</DT>\n    \t<DD>Spacing, layout and comments between source terms\n    \t(only when keep_comments option is in effect)</DD>\n    <DT>end_include</DT>\n    \t<DD>The end of an included source file</DD>\n    <DT>end</DT>\n    \t<DD>The end of the (top-level) source file</DD>\n    </DL>\n    The information about the source term itself is returned as a structure\n    <PRE>\n    :- export struct(source_term(\n\tterm,\t\t% the read term itself\n\tvars,\t\t% list of [VarName|Var] pairs (as in readvar/3)\n\tannotated       % the read term with type and source annotations \n\t...\n    )).\n    </PRE>\n    For category 'comment', the term is a string containing the comment.\n    For category 'end' and 'end_include', the term is the atom end_of_file.\n    In all these cases, vars is the empty list, and annotated is\n    uninstantiated.\n    <P>\n    Note that either the vars-field or the annotated field is valid,\n    depending on the setting of the with_annotations-option.  If the option\n    is set, the vars field contains an empty list, and the annotated term\n    is in the same format as that returned by read_annotated/2, i.e. a\n    recursive wrapper structure around the read term of the following format:\n<PRE>\n    :- export struct(annotated_term(\n        term,                   % var, atomic or compound\n                                % args of compound terms are annotated\n        type,                   % term type (see below)\n        file,                   % file name (atom)\n        line,                   % line number (integer >= 1)\n        from, to                % source position (integers >= 0)\n        ...\n    )).\n</PRE>\n    If the with_annotations-option is not set, the annotated-field remains\n    uninstantiated, and the vars-field is a list as detailed in readvar/3.\n    <P>\n    Notes on module handling:  When source_read/4 encounters a\n    module-directive (which is a handled_directive), the corresponding\n    module is implicitly created (unless it exists already, in which\n    case it is either reused or erased and re-created, depending on\n    the setting of the recreate_modules option), and that\n    module becomes the context module for any subsequently read\n    clauses or directives.  By default, source_close/2 removes these\n    modules again in order to restore the original state.\n    "), see_also : [source_open / 3, source_close / 2, readvar / 3, read_annotated / 2], amode : source_read(+, -, -, -)]).
