MODULE M3DepCompile; (***************************************************************************) (* Copyright (C) Olivetti 1989 *) (* All Rights reserved *) (* *) (* Use and copy of this software and preparation of derivative works based *) (* upon this software are permitted to any person, provided this same *) (* copyright notice and the following Olivetti warranty disclaimer are *) (* included in any copy of the software or any modification thereof or *) (* derivative work therefrom made by any person. *) (* *) (* This software is made available AS IS and Olivetti disclaims all *) (* warranties with respect to this software, whether expressed or implied *) (* under any law, including all implied warranties of merchantibility and *) (* fitness for any purpose. In no event shall Olivetti be liable for any *) (* damages whatsoever resulting from loss of use, data or profits or *) (* otherwise arising out of or in connection with the use or performance *) (* of this software. *) (***************************************************************************) IMPORT Fmt, Err, TextExtras, Text, SList, DirOp, AST, M3AST_AS, M3ASTWalk, M3Args, M3CFETool, M3CId, M3CImportS, M3CSpec, M3CTmpAtt, M3CUnit, M3CUnit_priv, M3Context, M3ContextRemove, M3Conventions, M3DepDATool, M3DepM3Path, M3Path; IMPORT M3AST_AS_F, M3AST_FE_F; VAR verbose_g, filter_g, filterExact_g: BOOLEAN := FALSE; datool_g: M3Args.T; CONST Int = M3CUnit.Type.Interface; Mod = M3CUnit.Type.Module; CurrentDir = ""; (* should be exported by M3Path *) TYPE DClosure = M3Context.Closure OBJECT changes: ARRAY M3CUnit.Type OF SList.T (* OF SList.TextElem *); OVERRIDES callback := Delete; END; Updates = ARRAY M3CUnit.Type OF M3DepM3Path.UpdateRec; PROCEDURE CheckVerbose() RAISES {}= BEGIN verbose_g := M3Args.GetFlag(M3DepDATool.Get(), M3DepDATool.Verbose_Arg); END CheckVerbose; (*PUBLIC*) PROCEDURE Run( c: M3Context.T; prev, cur: M3DepM3Path.T): INTEGER RAISES {}= VAR updates: Updates; BEGIN Clear(updates); datool_g := M3DepDATool.Get(); IF NOT M3Args.Find(datool_g) THEN RETURN -1 END; CheckVerbose(); GatherUpdates(prev, cur, updates); IF verbose_g THEN Err.Print("changes", Err.Severity.Comment); ListUnits(updates[Int], "interface"); ListUnits(updates[Mod], "module"); END; IF verbose_g THEN Err.Print("compiling", Err.Severity.Comment); END; RETURN FilteredCompile(c, cur, updates, CheckUpdateContext); END Run; PROCEDURE CheckFilterArg() RAISES {}= BEGIN filter_g := NOT M3Args.GetFlag(datool_g, M3DepDATool.NOFilterUnits_Arg); IF filter_g THEN filterExact_g := M3Args.GetFlag(datool_g, M3DepDATool.FilterUnitsExact_Arg); END; END CheckFilterArg; TYPE UpdateContextProc = PROCEDURE( context: M3Context.T; p: M3DepM3Path.T; updates: ARRAY M3CUnit.Type OF M3DepM3Path.UpdateRec; VAR (*out*) units: ARRAY M3CUnit.Type OF REF ARRAY OF TEXT ) RAISES {}; PROCEDURE FilteredCompile( c: M3Context.T; p: M3DepM3Path.T; VAR (*inout*) updates: Updates; up: UpdateContextProc;) : INTEGER RAISES {}= VAR f_updates: Updates; rc, trc := 0; units: ARRAY M3CUnit.Type OF REF ARRAY OF TEXT; BEGIN CheckFilterArg(); WHILE Filter(c, p, updates, f_updates) DO up(c, p, f_updates, units); trc := Compile(c, units); IF trc < 0 THEN rc := trc END; END; RETURN rc; END FilteredCompile; PROCEDURE Filter( c: M3Context.T; p: M3DepM3Path.T; VAR (*inout*) updates: Updates; VAR (*out*) f_updates: Updates; ): BOOLEAN RAISES {}= VAR elem: SList.TextElem; cu: M3AST_AS.Compilation_Unit; dir: M3Path.Elem; BEGIN IF filter_g OR filterExact_g THEN (* filter the set of units to those which are in the current directory or modules which "implement" interfaces in the context. We use a heuristic based on name for "implement" to avoid having to open the unit. Units scheduled for compilation are taken off the 'updates' list and placed on the 'f_updates' list *) FOR ut := FIRST(M3CUnit.Type) TO LAST(M3CUnit.Type) DO (* Deleted units are transferred en mass to f_updates *) f_updates[ut, M3DepM3Path.Update.Deleted] := updates[ut, M3DepM3Path.Update.Deleted]; updates[ut, M3DepM3Path.Update.Deleted].head := NIL; f_updates[ut, M3DepM3Path.Update.Changed].head := NIL; elem := updates[ut, M3DepM3Path.Update.Changed].head; (* Only keep these if we did when they were originally Added *) WHILE elem # NIL DO IF M3Context.Find(c, elem.text, ut, cu) THEN SList.AddRear(f_updates[ut, M3DepM3Path.Update.Changed], NEW(SList.TextElem, text := elem.text)); SList.Remove(updates[ut, M3DepM3Path.Update.Changed], elem); ELSE IF verbose_g THEN Err.Print(Fmt.F(" filtering %s %s", M3CUnit.TypeName(ut), elem.text), Err.Severity.Continue); END; END; (* if *) elem := elem.next; END; f_updates[ut, M3DepM3Path.Update.Added].head := NIL; elem := updates[ut, M3DepM3Path.Update.Added].head; (* Keep these if they are local, or if they are modules and implement an interface *) WHILE elem # NIL DO dir := M3DepM3Path.DirOf(p, ut, elem.text); IF IsCompileInDir(dir.text) OR ut = M3CUnit.Type.Module AND ImplementsAnInterface(c, elem.text) THEN SList.AddRear(f_updates[ut, M3DepM3Path.Update.Added], NEW(SList.TextElem, text := elem.text)); SList.Remove(updates[ut, M3DepM3Path.Update.Added], elem); ELSE IF verbose_g THEN Err.Print(Fmt.F(" filtering %s %s", M3CUnit.TypeName(ut), elem.text), Err.Severity.Continue); END; END; elem := elem.next; END; (* while *) END; (* for *) ELSE FOR ut := FIRST(M3CUnit.Type) TO LAST(M3CUnit.Type) DO FOR a := FIRST(M3DepM3Path.Update) TO LAST(M3DepM3Path.Update) DO f_updates[ut, a] := updates[ut, a]; updates[ut, a].head := NIL; END; (* for *) END; END; RETURN NOT Empty(f_updates); END Filter; PROCEDURE IsCompileInDir(d: TEXT): BOOLEAN RAISES {}= BEGIN IF Text.Equal(d, CurrentDir) THEN RETURN TRUE ELSE WITH cid = M3Args.GetStringList(datool_g, M3DepDATool.CompileInDir_Arg) DO IF cid = NIL THEN RETURN FALSE; ELSE FOR i := 0 TO NUMBER(cid^)-1 DO IF Text.Equal(d, cid[i]) THEN RETURN TRUE END; END; (* for *) RETURN FALSE; END; END; END; (* if *) END IsCompileInDir; PROCEDURE Empty(updates: Updates): BOOLEAN RAISES {}= BEGIN FOR ut := FIRST(M3CUnit.Type) TO LAST(M3CUnit.Type) DO FOR a := FIRST(M3DepM3Path.Update) TO LAST(M3DepM3Path.Update) DO IF updates[ut, a].head # NIL THEN RETURN FALSE END END; (* for *) END; (* for *) RETURN TRUE; END Empty; PROCEDURE Clear(VAR (*inout*) updates: Updates) RAISES {}= BEGIN FOR ut := FIRST(M3CUnit.Type) TO LAST(M3CUnit.Type) DO FOR a := FIRST(M3DepM3Path.Update) TO LAST(M3DepM3Path.Update) DO updates[ut, a].head := NIL; END; (* for *) END; (* for *) END Clear; PROCEDURE ImplementsAnInterface (c: M3Context.T; name: TEXT): BOOLEAN RAISES {} = BEGIN FOR ut := FIRST(M3CUnit.Type) TO LAST(M3CUnit.Type) DO IF ut IN M3CUnit.Interfaces THEN VAR iter := M3Context.NewIter(c, ut, FALSE); iname: TEXT; cu: M3AST_AS.Compilation_Unit; BEGIN WHILE M3Context.Next(iter, iname, cu) DO IF Implements(name, iname) THEN RETURN TRUE END; END; (* while *) END END; END; RETURN FALSE; END ImplementsAnInterface; PROCEDURE Implements(module, interface: TEXT): BOOLEAN RAISES {}= VAR li := Text.Length(interface); lm := Text.Length(module); index: CARDINAL := 0; BEGIN (* if filterExact_g then module = interface, else *module = interface OR module* = interface *) IF filterExact_g THEN RETURN Text.Equal(module, interface) ELSE IF TextExtras.FindSub(module, interface, index) THEN RETURN index = 0 OR (index = lm-li); ELSE RETURN FALSE; END; (* if *) END; END Implements; PROCEDURE Compile( c: M3Context.T; READONLY units: ARRAY M3CUnit.Type OF REF ARRAY OF TEXT; ): INTEGER RAISES {}= VAR phases: M3CUnit.Status; headerOnly: BOOLEAN; rc := 0; BEGIN CheckVerbose(); M3Context.Apply(c, NEW(M3Context.Closure, callback := ClearPrimarySource)); M3Args.SetStringList(M3CFETool.GetTool(), "Interfaces", units[Int]); M3Args.SetStringList(M3CFETool.GetTool(), "Modules", units[Mod]); IF M3Args.GetFlag(datool_g, M3DepDATool.CompileHeadersOnly_Arg) THEN phases := M3CUnit.Status{M3CUnit.State.Parsed, M3CUnit.State.ImportsResolved}; headerOnly := TRUE; ELSE phases := M3CUnit.AllPhases; headerOnly := FALSE; END; (* if *) IF M3CFETool.CompileInContext( c, phases, headerOnly) < 0 THEN rc := -1; END; (* attribute seting, only needed if CompileHeadersOnly or semantic analysis didnt occur (which it might not if IMPORT errors) *) M3Context.Apply(c, NEW(M3Context.Closure, callback := SetTmpAttrs)); RETURN rc; END Compile; (* PUBLIC *) PROCEDURE CompileUnits(c: M3Context.T; ut: M3CUnit.Type; units: REF ARRAY OF TEXT): INTEGER RAISES {}= VAR updates: ARRAY M3CUnit.Type OF M3DepM3Path.UpdateRec; a_units: ARRAY M3CUnit.Type OF REF ARRAY OF TEXT; void: M3AST_AS.Compilation_Unit; BEGIN Clear(updates); FOR i := 0 TO NUMBER(units^)-1 DO WITH elem = NEW(SList.TextElem, text := units[i]) DO IF M3Context.Find(c, units[i], ut, void) THEN WITH u = updates[ut, M3DepM3Path.Update.Changed] DO SList.AddRear(u, elem); END; ELSE WITH u = updates[ut, M3DepM3Path.Update.Added] DO SList.AddRear(u, elem); END; END; (* if *) END; END; (* for *) UpdateContext(c, NIL, updates, a_units); RETURN Compile(c, a_units); END CompileUnits; (* PUBLIC *) PROCEDURE CompileAll(c: M3Context.T; p: M3DepM3Path.T): INTEGER RAISES {}= VAR updates: Updates; inDir: TEXT := NIL; BEGIN CheckVerbose(); (* for each unit in the set associated with 'p', compile it unless it is already compiled with no errors. *) GatherUpdates(NIL, p, updates); CheckRemovedFromContextOrInError(c, Int, updates[Int, M3DepM3Path.Update.Added]); CheckRemovedFromContextOrInError(c, Mod, updates[Mod, M3DepM3Path.Update.Added]); RETURN FilteredCompile(c, p, updates, UpdateContext); END CompileAll; PROCEDURE CheckRemovedFromContextOrInError( c: M3Context.T; ut: M3CUnit.Type; VAR (*inout*) added: SList.T) RAISES {}= VAR elem: SList.TextElem := added.head; l := SList.T{}; BEGIN WHILE elem # NIL DO VAR cu: M3AST_AS.Compilation_Unit; BEGIN IF M3Context.Find(c, elem.text, ut, cu) AND cu.fe_status * M3CUnit.Errors = M3CUnit.Status{} THEN (* ok *) ELSE SList.AddRear(l, NEW(SList.TextElem, text := elem.text)); END; (* if *) END; elem := elem.next END; (* while *) added := l; END CheckRemovedFromContextOrInError; PROCEDURE ListUnits(u: M3DepM3Path.UpdateRec; tn: TEXT) RAISES {}= VAR mu: TEXT; elem: SList.TextElem; BEGIN FOR a := FIRST(M3DepM3Path.Update) TO LAST(M3DepM3Path.Update) DO elem := u[a].head; WHILE elem # NIL DO IF a = M3DepM3Path.Update.Added THEN mu := " - added"; ELSIF a = M3DepM3Path.Update.Deleted THEN mu := " - deleted"; ELSE mu := " - changed"; END; Err.Print(" " & tn & " " & elem.text & mu, Err.Severity.Continue); elem := elem.next; END; (* while *) END; (* for *) END ListUnits; PROCEDURE CheckUpdateContext( context: M3Context.T; p: M3DepM3Path.T; updates: ARRAY M3CUnit.Type OF M3DepM3Path.UpdateRec; VAR (*out*) units: ARRAY M3CUnit.Type OF REF ARRAY OF TEXT ) RAISES {}= BEGIN CheckContext(context, Int, p, updates[Int, M3DepM3Path.Update.Added]); CheckContext(context, Int, p, updates[Int, M3DepM3Path.Update.Changed]); CheckContext(context, Mod, p, updates[Mod, M3DepM3Path.Update.Added]); CheckContext(context, Mod, p, updates[Mod, M3DepM3Path.Update.Changed]); UpdateContext(context, NIL, updates, units); END CheckUpdateContext; PROCEDURE UpdateContext( context: M3Context.T; void: M3DepM3Path.T; updates: ARRAY M3CUnit.Type OF M3DepM3Path.UpdateRec; VAR (*out*) units: ARRAY M3CUnit.Type OF REF ARRAY OF TEXT ) RAISES {}= VAR cl := NEW(DClosure); int_updates := updates[Int]; mod_updates := updates[Mod]; BEGIN cl.changes[Int].head := NIL; cl.changes[Mod].head := NIL; (* we want to end up with a new list to compile *) (* trash the deleted modules *) IF int_updates[M3DepM3Path.Update.Deleted].head # NIL OR mod_updates[M3DepM3Path.Update.Deleted].head # NIL THEN IF verbose_g THEN Err.Print("removing deleted units", Err.Severity.Comment); END; M3ContextRemove.Units(context, ArrayFromSList(int_updates[M3DepM3Path.Update.Deleted])^, M3CUnit.Type.Interface, cl); M3ContextRemove.Units(context, ArrayFromSList(mod_updates[M3DepM3Path.Update.Deleted])^, M3CUnit.Type.Module, cl); END; IF int_updates[M3DepM3Path.Update.Changed].head # NIL OR mod_updates[M3DepM3Path.Update.Changed].head # NIL THEN IF verbose_g THEN Err.Print("removing changed units", Err.Severity.Comment); END; M3ContextRemove.Units(context, ArrayFromSList(int_updates[M3DepM3Path.Update.Changed])^, M3CUnit.Type.Interface, cl); M3ContextRemove.Units(context, ArrayFromSList(mod_updates[M3DepM3Path.Update.Changed])^, M3CUnit.Type.Module, cl); END; MergeLists(int_updates[M3DepM3Path.Update.Added], cl.changes[M3CUnit.Type.Interface]); MergeLists(mod_updates[M3DepM3Path.Update.Added], cl.changes[M3CUnit.Type.Module]); units[Int] := ArrayFromSList(cl.changes[Int]); units[Mod] := ArrayFromSList(cl.changes[Mod]); END UpdateContext; PROCEDURE CheckContext( c: M3Context.T; ut: M3CUnit.Type; p: M3DepM3Path.T; VAR (*inout*) updates: SList.T (* OF SList.TextElem *)) RAISES {}= VAR elem: SList.TextElem := updates.head; BEGIN (* We may have explicitly compiled this already, check timestamps *) WHILE elem # NIL DO VAR cu: M3AST_AS.Compilation_Unit; BEGIN IF M3Context.Find(c, elem.text, ut, cu) AND M3DepM3Path.UidEqual(p, elem.text, ut, cu.fe_uid) THEN SList.Remove(updates, elem); END; (* if *) END; elem := elem.next; END; (* while *) END CheckContext; PROCEDURE MergeLists(m: SList.T; VAR (*inout*) l: SList.T) RAISES {}= VAR elem: SList.TextElem := m.head; BEGIN WHILE elem # NIL DO AddND(l, elem.text); elem := elem.next; END; (* for *) END MergeLists; PROCEDURE GatherUpdates(p_old, p: M3DepM3Path.T; VAR (*out*) updates: Updates)= VAR inDir: TEXT := NIL; inDirs: REF ARRAY OF TEXT; inDirElem: M3Path.Elem := NIL; t_updates: Updates; BEGIN (* look for changed interfaces everywhere *) M3DepM3Path.Interfaces(p_old, p, updates[Int]); (* look for changed modules in current + CompileInDir_Arg dirs *) WITH cid = M3Args.GetStringList(datool_g, M3DepDATool.CompileInDir_Arg) DO IF cid = NIL THEN (* arg unset, compile current only *) inDirs := NEW(REF ARRAY OF TEXT, 1); inDirs[0] := CurrentDir; ELSE IF NUMBER(cid^) # 0 THEN inDirs := NEW(REF ARRAY OF TEXT, NUMBER(cid^)+1); inDirs[0] := CurrentDir; FOR d := 0 TO NUMBER(cid^)-1 DO inDirs[d+1] := cid^[d]; END; (* for *) ELSE inDirs := NEW(REF ARRAY OF TEXT, 1); inDirs[0] := NIL; END; END; FOR d := 0 TO NUMBER(inDirs^)-1 DO Clear(t_updates); IF inDirs[d] = NIL OR ValidatedDir(p, inDirs[d], inDirElem) THEN M3DepM3Path.Modules(p_old, p, t_updates[Mod], inDirElem); FOR k := FIRST(M3DepM3Path.Update) TO LAST(M3DepM3Path.Update) DO JoinLists(t_updates[Mod, k], updates[Mod, k]); END; (* for *) ELSE Err.Print(Fmt.F("directory: \'%s\' not on search path \n", inDirs[d]), Err.Severity.Warning); END; (* if inaccessible *) END; (* for *) END; END GatherUpdates; PROCEDURE ValidatedDir(p: M3DepM3Path.T; dir: TEXT; VAR (*out*) e: M3Path.Elem): BOOLEAN RAISES {}= BEGIN e := M3DepM3Path.ValidateDir(p, dir); RETURN e # NIL; END ValidatedDir; PROCEDURE JoinLists(l1: SList.T; VAR (*inout*) l2: SList.T) RAISES {}= BEGIN IF l1.head = NIL THEN RETURN END; IF l2.head = NIL THEN l2.head := l1.head ELSE VAR t: SList.Elem := l2.head; BEGIN WHILE t.next # NIL DO t := t.next; END; (* while *) t.next := l1.head; END; END; (* if *) END JoinLists; PROCEDURE Delete( cl: DClosure; ut: M3CUnit.Type; name: TEXT; cu: M3AST_AS.Compilation_Unit) RAISES {}= BEGIN IF verbose_g THEN Err.Print(Fmt.F(" %s %s removed from context", M3CUnit.TypeName(ut), name), Err.Severity.Continue); END; AddND(cl.changes[IntOrMod(ut)], name); END Delete; PROCEDURE IntOrMod(ut: M3CUnit.Type): M3CUnit.Type RAISES {}= BEGIN CASE ut OF | M3CUnit.Type.Interface, M3CUnit.Type.Interface_gen_def, M3CUnit.Type.Interface_gen_ins => RETURN M3CUnit.Type.Interface; | M3CUnit.Type.Module, M3CUnit.Type.Module_gen_def, M3CUnit.Type.Module_gen_ins => RETURN M3CUnit.Type.Module; END; (* case *) END IntOrMod; PROCEDURE AddND(VAR l: SList.T; name: TEXT) RAISES {}= VAR head: SList.TextElem := l.head; BEGIN WHILE head # NIL DO IF Text.Equal(head.text, name) THEN RETURN END; (* if *) head := head.next; END; (* while *) SList.AddRear(l, NEW(SList.TextElem, text := name)); END AddND; TYPE TmpAttClosure = M3ASTWalk.Closure OBJECT cu: M3AST_AS.Compilation_Unit; OVERRIDES callback := SetTmpAtt; (* new default method *) END; (*PRIVATE*) PROCEDURE ClearPrimarySource( cl: M3Context.Closure; ut: M3CUnit.Type; name: Text.T; cu: M3AST_AS.Compilation_Unit) RAISES {}= BEGIN M3CUnit.ExclState(cu.fe_status, M3Conventions.PrimarySource); END ClearPrimarySource; (*PRIVATE*) PROCEDURE SetTmpAttrs( cl: M3Context.Closure; ut: M3CUnit.Type; name: Text.T; cu: M3AST_AS.Compilation_Unit) RAISES {}= (* Do a tree walk, and call SetTmpAtt for every node. Also call M3CImportS.Set. *) BEGIN cu := M3CUnit.ToGenIns(cu, ut); IF NOT M3CUnit.State.SemChecked IN cu.fe_status THEN M3ASTWalk.VisitNodes(cu, NEW(TmpAttClosure, cu := cu)); M3CImportS.Set(cu.as_root); END; END SetTmpAttrs; (*PRIVATE*) PROCEDURE SetTmpAtt( cl: TmpAttClosure; an: AST.NODE; vm: M3ASTWalk.VisitMode) RAISES {}= BEGIN IF an = NIL THEN RETURN; END; M3CTmpAtt.SetTmpUnitId(an, cl.cu.as_root.as_id); M3CSpec.Set(an); (* sm_spec, sm_comp_unit *) END SetTmpAtt; (*PRIVATE*) PROCEDURE ArrayFromSList(sl: SList.T): REF ARRAY OF TEXT RAISES {} = VAR a := NEW(REF ARRAY OF TEXT, SList.Length(sl)); e: SList.TextElem; BEGIN e := sl.head; FOR i := FIRST(a^) TO LAST(a^) DO a[i] := e.text; e := e.next; END; RETURN a; END ArrayFromSList; BEGIN END M3DepCompile.