%%% *************************************************************
%%% Copyright (C) 2002-2005 Torsten Anders (www.torsten-anders.de)
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License
%%% as published by the Free Software Foundation; either version 2
%%% of the License, or (at your option) any later version.
%%% This program 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 the
%%% GNU General Public License for more details.
%%% *************************************************************
/** %% This functor defines some general utilities
%% */
functor
import
System Compiler
Module OS Combinator Property QTk at 'x-oz://system/wp/QTk.ozf' % Tk
FD FS RecordC
LUtils at 'ListUtils.ozf'
Out at 'Output.ozf'
% Browser(browse:Browse) % temp for debugging
export
Pi
XOr Cases
MakeTypecheck_NotKinded
isRecord: IsRecord2
isAtom: IsAtom2
IsEqual
IsFS MakeSingletonSet IntsToFS
RelationComplement ConstrainRelation ConstrainRelationR
ReifiedDistance
Percent
Identity
Random
DevRandom
KnuthRandom SetKnuthRandomSeed
RandIntoRange RandIntoRange2
RoundDigits
MakeRandomGenerator SetRandomGeneratorSeed
Log Mod_Float IsDivisible
RatioToFloat
IsRatio
RecursiveRatio
PrimeFactors
%% MakeConcurrentFn
ToProc ToFun Procs2Proc
ExtendedScriptToScript
ApplySelected
EncodeRatio
% SelectArg
TimeVString TimeForFileName
GetCounterAndIncr ResetCounter
UnarySkip BinarySkip
TakeFeatures
RecursiveAdjoin
KeepList
ModuleLink ModuleApply
TimeSpend
Assert
WarnGUI InfoGUI ErrorGUI
define
/** %% The mathematical constant pi.
%% */
%% this is as much precision as Oz allows
Pi = 3.141592653589793
/** %% Defines exclusive or: XOr returns true if only B1 or B2 are true. XOr returns false if B1 and B2 are both false or both true.
%%*/
fun {XOr B1 B2}
% NOTE: !!?? could this be more efficient?
{And
{Or B1 B2}
{Not {And B1 B2}}}
end
/** %% Cases defines a general conditional similar to an 'if then elseif ...' statement. X is some datum to process dependent on boolean tests. Clauses is a list of test and action functions/methods in the form [Test1#Process1 Test2#Process2 ...]. The first test returning true for X 'fires' its Process and Cases returns the result of {Process X}. If no test returns true for X Cases returns nil.
%% */
fun {Cases X Clauses}
SucceededClause = {LUtils.find Clauses
fun {$ Test#_} {{ToFun Test} X} end}
in
if SucceededClause==nil then nil
else
_#Process = SucceededClause
in
{{ToFun Process} X}
end
end
/** %% Mozart typechecks like IsRecord block on kinded variables. MakeTypecheck_NotKinded returns a variant of the type test MyTest (a Boolean function) which immediates returns false for kinded variables.
%% */
fun {MakeTypecheck_NotKinded MyTest}
fun {$ X}
{Not {IsKinded X}} andthen {MyTest X}
end
end
IsRecord2 = {MakeTypecheck_NotKinded IsRecord}
IsAtom2 = {MakeTypecheck_NotKinded IsAtom}
/** %% Test equality with either '==' or System.eq (same value node in the store). E.g., records can be '==' while System.eq returns false and for objects its the other way round.
%% */
fun {IsEqual X Y}
{Value.'==' X Y} orelse {System.eq X Y}
end
/** %% IsFS returns true if X is a FS variable (determined or not) and false otherwise. This function is necessary, because the primitive Oz functions FS.var.is and FS.value.is behave differently for determined and undetermined FS variables.
%% */
fun {IsFS X}
if {IsDet X}
%% returns true for determined FS and blocks otherwise
then {FS.value.is X}
%% !! returns false for determined FS
else {FS.var.is X}
end
end
/** %% Expects D (a FD int) and returns a singleton FS which contains only D.
%% */
proc {MakeSingletonSet D ?MyFS}
MyFS = {FS.var.decl}
{FS.include D MyFS}
{FS.card MyFS 1}
end
/** %% Constraints that Ds (a list of FD ints) are all contained in MyFS (a FS, implicitly declared), but no other integer. This definition is similar to FS.int.match, but Ds must not be in increasing order.
%% */
proc {IntsToFS Ds MyFS}
MyFS = {FS.var.decl}
%% not necessary, already derived anyway
% {FS.card MyFS} =<: {Length Ds}
{FS.unionN {Map Ds fun {$ D} {MakeSingletonSet D} end}
MyFS}
end
/** %% Expects a FD relation atom ('<:', '=<:', '>:', '>=:', '=:', or '\\=:') and returns the complement. For example, the complement of '<:' is '>:', of '>=:' is '=<:' and of '=:' is '\\=:'.
%% */
fun {RelationComplement Dir}
case Dir
of '>:' then '<:'
[] '>=:' then '=<:'
[] '<:' then '>:'
[] '=<:' then '>=:'
[] '=:' then '\\=:'
[] '\\=:'then '=:'
end
end
/** %% Constraints the relation between the FD ints X and Y according to A, a FD relation atom ('<:', '=<:', '>:', '>=:', '=:', or '\\=:').
%% */
proc {ConstrainRelation X Y A}
case A
of '>:' then X >: Y
[] '>=:' then X >=: Y
[] '<:' then X <: Y
[] '=<:' then X =<: Y
[] '=:' then X =: Y
[] '\\=:' then X \=: Y
end
end
/** %% Reified version of BinRelation.
%% */
fun {ConstrainRelationR X Y A}
case A
of '>:' then X >: Y
[] '>=:' then X >=: Y
[] '<:' then X <: Y
[] '=<:' then X =<: Y
[] '=:' then X =: Y
[] '\\=:' then X \=: Y
end
end
/** %% Simple substitute for FD.reified.distance, which does not propagate enough.
%% E.g., check {Browse {FD.reified.distance 64 62 '>:' 3}}, which should return 0 and not {FD.int 0#1}.
%% */
proc {ReifiedDistance X Y Rel Z B}
Dist = {FD.decl}
in
{FD.distance X Y '=:' Dist}
B = {ConstrainRelationR Dist Z Rel}
end
%% FS.int.match is better than this..
% /** %% Expects MyFS (a FS) and returns Ds, a list of FD ints which are all contained in MyFS.
% %% This definition is similar to FS.int.match, but Ds must not be in increasing order. This definiiton is also similar to IntsToFS, but Ds are created by FsToInts. The length of Ds is the cardiality of MyFS, and all elements in Ds are constrained to be pairwise distinct.
% %%
% %% Note: blocks until cardiality of MyFS is determined
% %% */
% proc {FsToInts MyFS ?Ds}
% Ds = {FD.list {FS.card MyFS} 0#FD.sup}
% in
% {FS.unionN {Map Ds fun {$ D} {MakeSingletonSet D} end}
% MyFS}
% {FD.distinct Ds}
% end
/** %% Constrains percentage of N (FD int) if NoAll (FD int) indicates 100 percent. Result is implicitly declared a FD int.
%% Example: {Percent 4 6} = 66
%% Note the rounding to 66 percent (better do not rely on exact value of Result but constraint it, e.g., by a comparison such as Result >: 50).
%% */
proc {Percent N NoAll Result}
Aux = {FD.decl}
in
Result = {FD.int 0#100}
Aux =: N * 100
Result = {FD.divI Aux NoAll}
end
/** %% The Identity function returns its argument.
%% */
fun {Identity X} X end
%%
%% Numeric Utils
%%
/** %% Returns a random integer in interval [0, Max-1].
%% */
fun {Random Max}
% MinOS is ignored but needs to be 0
MinOS=0 MaxOS
in
{OS.randLimits MinOS MaxOS}
{Int.'div' ({OS.rand}*Max) MaxOS}
end
/** %% Returns a random number generated by /dev/random (see man 4 random).
%% Note: this function is not extremely efficient, but very useful for generating random seeds.
%% */
fun {DevRandom}
{Compiler.virtualStringToValue
{Out.execWithOutput 'od' ['-An' '-N2' '-i' '/dev/random']}}
end
local
MaxRand = {Pow 2 64} % BTW, {OS.randLimits 0} is 2^31-1
LastVal = {NewCell {DevRandom}}
in
/** %% Sets the seed for KnuthRandom. If Seed is 0 then {GUtils.devRandom} is used.
%% */
proc {SetKnuthRandomSeed Seed}
ActualSeed = if Seed==0 then {DevRandom} else Seed end
in
LastVal := ActualSeed
end
/** %% Returns a random number between 0 and 2^64. Implements a linear congruential generator with parameters proposed by Donald Knuth for MMIX (see http://en.wikipedia.org/wiki/Linear_congruential_generator).
%% */
fun {KnuthRandom}
LastVal := (6364136223846793005 * @LastVal + 1442695040888963407) mod MaxRand
@LastVal
end
end
local
OS_MaxRand = {OS.randLimits 0}
in
/** %% Expects a random integer between 0 and {OS.randLimits 0} (e.g., generated by {OS.rand}) and returns a random integer in Min - Max (Min and Max are integers).
%% */
fun {RandIntoRange Rand Min Max}
{RandIntoRange2 Rand Min Max OS_MaxRand}
end
/** %% Expects a random number Rand between 0 and MaxRand and returns a random integer in Min - Max (Min and Max are integers).
%% */
fun {RandIntoRange2 Rand Min Max MaxRand}
(Rand * (Max - Min + 1)) div MaxRand + Min
end
end
/** %% Rounds F (a float) to Digits (an int) number of digits after the floating point.
%%
%% Note: the accuracy of this rounding is subject to float accuracy..
%% */
fun {RoundDigits F Digits}
Fac = {IntToFloat {Pow 10 Digits}}
in
{Round F * Fac} / Fac
end
local
fun lazy {RandomStream} {KnuthRandom}|{RandomStream} end
% fun lazy {RandomStream} {OS.rand}|{RandomStream} end
RandomNumbers={NewCell {RandomStream}}
in
/** %% Returns a random number generator (a null-ary function) which returns a pseudo-random integer whenever it is called. Every returned random number generator will always produce the same number sequence: all random values are 'recorded' behind the scene in the top-level space. In other words, the random number generator is deterministic. Such a random generator can be used for a randomised value ordering, and the resulting distribution strategy can still apply recomputation (see SDistro.makeRandomDistributionValue). In such as case, MakeRandomGenerator must be called inside script. The convenient Strasheela solvers in SDistro do that implicitly.
%% Only a single instance of a random number generator can be used at a time. This instance can be (re)-initialised with SetRandomGeneratorSeed.
%% */
%%
%% This implementation is based on a suggestion by Raphael Collet (emails Wed, 02 Feb 2005 to users@mozart-oz.org).
%% If MakeRandomGenerator is called inside a script, then the cell Str (see below) is local to that script and can thus be statefully changed in the script by the proc returned by MakeRandomGenerator.
fun {MakeRandomGenerator}
Str={NewCell @RandomNumbers}
in
proc {$ ?X} T in X|T=Str:=T end
end
/** %% Sets the seed for the random number generator used by MakeRandomGenerator (which internally uses OS.rand). If Seed is 0, the seed will be generated from the current time.
%% NOTE: calling SetRandomGeneratorSeed will corrupt any random number generator previously created with MakeRandomGenerator. Either call {SetRandomGeneratorSeed 0} only once after starting Mozart (so a 'random' seed is used), or re-feed your code calling MakeRandomGenerator after using SetRandomGeneratorSeed (e.g., re-call your solver).
%% */
proc {SetRandomGeneratorSeed Seed}
% {OS.srand Seed}
{SetKnuthRandomSeed Seed}
RandomNumbers:={RandomStream}
end
end
/** %% Returns the logarithm to the base Base of X. X and Base must be floats and a float is returned.
%% */
fun {Log X Base}
%% ? more efficient would be to define, e.g., Log2 which
%% evaluates {Float.log 2} only once. But I prefered this more
%% general and clean definition.
{Float.log X} / {Float.log Base}
end
/** %% Similar to the mod operation, but arguments and return value are floats.
%% */
fun {Mod_Float X1 X2}
Result = X1 - X2 * {IntToFloat ({FloatToInt X1} div {FloatToInt X2})}
in
%% Result be neg
if Result < 0.0
then Result + X2
else Result
end
end
/** %% Returns a Boolean value whether X is divisible by Y. X and Y are ints.
%% */
fun {IsDivisible X Y}
%% Implementation is approximated using floats for simplicity
% {Abs {IntToFloat X}/{IntToFloat Y} - {IntToFloat X div Y}} < 0.5 / {IntToFloat Y}
X div Y * Y == X
end
/** %% Expects a ratio spec Nom#Denom and returns the corresponding float.
%% */
fun {RatioToFloat Nom#Denom}
{IntToFloat Nom} / {IntToFloat Denom}
end
/** %% Returns true if X is a pair of ints Nom#Denom.
%% */
fun {IsRatio X}
{IsRecord2 X} andthen {Label X} == '#'
andthen {Width X}==2 andthen {All {Record.toList X} IsInt}
end
/** %% Expects a ratio X#Y and returns Y#X.
%% */
fun {RecursiveRatio X#Y} Y#X end
/** %% Expects an int X and returns its prime factors (a list of ints in ascending order). Note: only primes up to 23 are tested, larger prime numbers are simply ignored.
%% */
fun {PrimeFactors X}
PrimesToTest = unit(2 3 5 7 11 17 19 23)
PrimesL = {Width PrimesToTest}
FoundPrimes = unit(...)
fun {TestPrime X PrimeI}
Prime = PrimesToTest.PrimeI
in
if X == 1
%% return result
then {RecordC.reflectArity FoundPrimes}
elseif {IsDivisible X Prime}
then
FoundPrimes ^ Prime = unit
{TestPrime (X div Prime) PrimeI}
else
if PrimeI==PrimesL
%% return result
then {RecordC.reflectArity FoundPrimes}
else {TestPrime X PrimeI+1}
end
end
end
in
{TestPrime X 1}
end
%%
%% Constraint programming utils
%%
/** %% Q encodes X/Y by an integer as X/Y * Factor. Possible values for X/Y depend on Factor, e.g., 1/3 can not truely be represented if Factor=2. Factor should be an determined integer. For example, if Factor=12 then Q can represent 1/6 (Q=2), 1/4 (Q=3) etc.
%% */
proc {EncodeRatio X Y Factor Q}
X * Factor =: Y * Q
end
% proc {EncodeInteger X Y Summand Z}
% end
/** %% The Ith element in Procs is applied. Procs is list of null-ary procedures. I is a FD integer, the domain of I is implicitly reduced to 1#{Length Procs}.
%% This is quasi a selection constraint, however, there are no constraint propagators created by ApplySelected. Instead, ApplySelected uses the deep-guard combinator Combinator.'or', i.e. a backtracking-free disjunction. ApplySelected suspends until a decision is made elsewhere (e.g. by determining I or by ruling out the cause of the application of all but one procedure in Procs).
%%
%% See also Pattern.transformDisj
%% */
%% !!?? shall this go into extra Constraints functor?
proc {ApplySelected Procs I}
ProcsTuple = {List.toTuple '#' Procs}
I :: 1#{Length Procs} % just to make sure...
in
{Combinator.'or' {Record.mapInd ProcsTuple
fun {$ ProcI Proc}
proc {$}
I = ProcI
{Proc}
end
end}}
end
%%
%% Concurrent Utils
%%
% /* %% Returns a concurrent version of the unary function Fn.
% %%*/
% fun {MakeConcurrentFn Fn}
% fun {$ X} thread {Fn X} end end
% end
%%
%% OOP Utils
%%
/** %% Function ToProc transforms a method to a procedure. The argument X represents the method and its interface. X may be an atom (representing a method with no argument), or a record (e.g. representing a method with multiple arguments). For convenience, X may also be a procedure, which will be returned unchanged.
%%
%% The returned procedure expects one, two or three arguments. The first argument is always the object to which the method is passed. If X is an atom, this is the only argument. E.g. {ToProc test}
returns the procedure proc {$ O} {O test} end
.
%%
%% If the returned procedure expects more than only one argument, the last argument of the procedure is always the value at feature 1 of the method record. In Strasheela, the first method feature is usually defined as the return value of the method. If the method expects only that argument, the procedure returned expects two arguments. E.g. {ToProc isTest(x)}
results in the procedure proc {$ O Result} {O test(Result)} end
.
%%
%% If the method defines multiple arguments, all other arguments are collected in a record in the second argument of the procedure. E.g. {ToProc isTest(x test:MyTest)}
results in the procedure proc {$ O Args Result} {O test(Result test:Args.test)} end
. Note that this example was simplified, as all arguments in Arg are optional. If Args.test is not given above, then the procedure is proc {$ O Args Result} {O test(Result test:MyTest)} end
. However, the argument specifier at feature 1 always only a "dymmy value" that indicates the return value as in the example above.
%%*/
proc {ToProc X ?Res}
%% !! resulting procedures are less efficient then methods
%% (need to construct method record for each proc call)
Res =
if {IsProcedure X}
then X
elseif {IsAtom2 X}
then proc {$ O} {O X} end % method with no arg
elseif {IsRecord2 X}
then
%% first feature of method is always result
if {Arity X} == [1]
then
proc {$ O Result} % method with single arg
M = {MakeRecord {Label X} [1]}
in
M.1 = Result
{O M}
end
%% record with arity > 1
else
proc {$ O Args Result} % method with multiple args
FullArgs = {Record.subtract {Adjoin X Args} 1}
M = {MakeRecord {Label X} 1|{Arity FullArgs}}
in
M.1 = Result
{Record.forAllInd FullArgs proc {$ I X} M.I=X end}
{O M}
end
end
else
{Exception.raiseError
kernel(type
ToProc [X Res] % args
'procedure, atom, or record' % type
1 % arg position
"Either a procedure, atom, or a record required."
)}
unit % never returned
end
end
/** %% Transforms an atom -- representing the label of a unary method -- into a unary function which expects as argument the object the method shall be send to. For convenience, X may also be a procedure, which will be returned unchanged.
%%*/
proc {ToFun X ?Res}
Res =
if {IsProcedure X} % !! do I need this option ?
then X
elseif {IsAtom2 X}
then {ToProc {MakeRecord X [1]}}
% raise type error: neither procedure nor atom
else
{Exception.raiseError
kernel(type
ToFun [X Res] % args
'procedure or atom' % type
1 % arg position
"Either a procedure or an atom required."
)}
unit % never returned
end
end
/** %% Returns a single unary procedure which applies all elements in Procs -- a list of unary procedures -- to its argument (example application: transforms a list of unary compositional rules into a single rule).
% */
fun {Procs2Proc Procs}
proc {$ X}
proc {Aux Procs X}
if Procs==nil
then skip
else {Procs.1 X} {Aux Procs.2 X}
end
end
in
{Aux Procs X}
end
end
/* %% Unary procedure which does nothing.
%% */
proc {UnarySkip X} skip end
/* %% Binary procedure which does nothing.
%% */
proc {BinarySkip X Y} skip end
/** %% Convenience function for parameterised CSP scripts. An extended script is a binary procedure, i.e., a script where the first argument is a record of arguments expected by the script and the second argument is the script root variable.
%% ExtendedScriptToScript expects an extended script plus its Args, and returns a plain script (i.e. a unary procedure).
%% */
%% !!?? put into ScoreDistro.oz?
fun {ExtendedScriptToScript MyExtendedScript Args}
proc {$ Sol} Sol = {MyExtendedScript Args} end
end
% /** % SelectArg is a tool, e.g., to define functions with quasi optional values. SelectArg returns the value at Feature in record Spec, if Spec has this feature. Otherwise the value at Feature in the record Defaults is returned. Defaults must have this record.
% %% !! Often the buildin Adjoin is a better solution: {Adjoin Defaults Args} = EffectiveArgs
% %% */
% %% !!?? Shall I remove this?
% fun {SelectArg Feature Spec Default}
% if {HasFeature Spec Feature}
% then Spec.Feature
% else Default
% end
% end
/** % Returns a VS of the current time in the form
%% 'hour:min:sec, day-month-year'.
%% */
%% !!?? Do I need this defined here (local def in Output.oz enough)?
fun {TimeVString}
Time = {OS.localTime}
in
Time.hour#':'#Time.min#':'#Time.sec#', '#Time.mDay#'-'#Time.mon+1#'-'#Time.year+1900
end
/** %% Returns a VS of the current time in the form
%% day-month-year_hour-min-sec.
%%
%% This format does not contain any characters that might be problematic in a file name.
%% */
fun {TimeForFileName}
MyTime = {OS.localTime}
in
MyTime.mDay#'-'#MyTime.mon+1#'-'#MyTime.year+1900#'_'#MyTime.hour#'-'#MyTime.min#'-'#MyTime.sec
end
local
Counter = {Cell.new 1}
in
/** %% Return an integer and as a side effect increment the integer for the next access.
%% */
proc {GetCounterAndIncr ?X}
X = {Cell.access Counter}
{Cell.assign Counter X+1}
end
/** %% Resets the counter for GetCounterAndIncr.
%% */
proc {ResetCounter}
Counter := 1
end
end
/** %% Fun R (a record) and MyFeats (a list of symbols -- potential features in R). TakeFeatures returns a record which consists in all features and their values of MyFeats contained in R.
%% */
fun {TakeFeatures R MyFeats}
{Record.filterInd R
fun {$ Feat X}
{Member Feat MyFeats}
end}
end
/** %% Like Adjoin, but nested records are processed recursively.
%% R1 and R2 must have the same nesting for recursive processing, otherwise R2 features are taken (like Adjoin).
%% NB: lists and pairs are treated as elementary values, only 'normal' records are processed recursively.
%% */
proc {RecursiveAdjoin R1 R2 ?Result}
Feats = {LUtils.removeDuplicates {Append {Arity R1} {Arity R2}}}
%% X is record but neither list nor pair
fun {IsNormalRecord X}
{IsRecord2 X}
andthen {Not {IsList X}}
andthen {Not {Label X}=='#'}
andthen {Not {IsAtom2 X}}
end
in
Result = {MakeRecord {Label R2} Feats}
{Record.forAllInd Result
proc {$ Feat X}
if {HasFeature R2 Feat} then
if {IsNormalRecord R2.Feat}
andthen {HasFeature R1 Feat}
andthen {IsNormalRecord R1.Feat}
%% nested case
then X = {RecursiveAdjoin R1.Feat R2.Feat}
else X = R2.Feat
end
else %% only R1 has feature Feat
X = R1.Feat
end
end}
end
/** %% Opposite of Record.subtractList: returns a record like R, but only keeps the features Fs. Fs can contain features not present in R, but these will then be skipped.
%% */
fun {KeepList R Fs}
Feats = {Filter {Arity R}
fun {$ Feat} {Member Feat Fs} end}
in
{Record.mapInd {MakeRecord {Label R} Feats}
fun {$ Feat _} R.Feat end}
end
local
ModMan = {New Module.manager init}
in
/** %% ModuleLink is like Module.link except that multiple calls of ModuleLink share the same module manager (and don't create new managers as Module.link does). For instance, when ModuleLink links multiple functors which refer to a stateful datum in some functor, then all refer to the same datum instance. By constrast, linking with Module.link results into multiple stateful datum instances.
%% !! On second though, ModuleLink seems to solve a non-existing problem. ModuleLink is an attempt to avoids problems in case some functor is linked more then once in the OPI. Actually, this should happen only in two cases: either you want to create two module instances (with independent stateful data) or you want to reload a functor (e.g. after compilation) without restarting the whole program. In both cases, Module.link does the right thing. So, why did I ever need ModuleLink???
%% -> A buffer with this ModuleLink can be re-fed multiple times without problems. A call to Module.link should not be re-fed...
%% */
fun {ModuleLink UrlVs}
{Map UrlVs fun {$ Url}
{ModMan link(url:Url $)}
end}
end
/** %% ModuleApply is like Moduel.apply expect that it always uses the same module manager (cf. ModuleLink).
%% */
fun {ModuleApply UFs}
{Map UFs fun {$ UF}
case UF of U#F then
{ModMan apply(url:U F $)}
else
{ModMan apply(UF $)}
end
end}
end
end
/** %% Returns the time (in msecs) the application of P (a null-ary procedure) took.
%% */
fun {TimeSpend P}
Start End
in
Start = {Property.get 'time.total'}
{P}
End = {Property.get 'time.total'}
End - Start
end
/** %% If B is false, then MyException is raised.
%% */
%% !!?? Could calling Assert be inefficient so that I would like to turn it of globally?
%% Possible implemementation: B is either a boolean or a 0-ary boolean fun. When Assert is switched of globally (e.g. using some Strasheela env var), then the tests executed by all the functions are not executed (when the test is not wrapped in a function, but excuted directly then it is always executed). That way, expensive tests could be avoided globally.
%% Anyway, for now this is overkill. If I realise that things are too inefficient and much time is spend in Assert I could add this feature later.
proc {Assert B MyException}
if {Not B}
then {Exception.raiseError MyException}
end
end
%%
%% GUI messages
%%
%% see http://aspn.activestate.com/ASPN/docs/ActiveTcl/8.4/tcl/TkCmd/messageBox.htm for additional args of tk_messageBox
%%
/** %% Opens a warning dialog which displays VS.
%% */
proc {WarnGUI VS}
Window = {QTk.build td(text(init:"WARNING: "#VS
height:5
width:50
wrap:word
background:yellow)
button(text:"OK"
action:toplevel#close))}
in
{Window show}
%%
%% NB: blocks, until OK buttom is pressed, and a surrounding thread does not help against this.
% %% returns ok if ok button is pressed..
% _ = {Tk.return tk_messageBox(icon:warning
% type:ok
% message:VS)}
end
/** %% Opens a warning dialog which displays VS.
%% */
proc {InfoGUI VS}
Window = {QTk.build td(text(init:"INFO: "#VS
height:5
width:50
wrap:word)
button(text:"OK"
action:toplevel#close))}
in
{Window show}
% %% NB: blocks, until OK buttom is pressed, and a surrounding thread does not help against this.
% %% returns ok if ok button is pressed..
% _ = {Tk.return tk_messageBox(icon:info
% type:ok
% message:VS)}
end
/** %% Opens an error dialog which displays VS.
%% */
proc {ErrorGUI VS}
Window = {QTk.build td(text(init:"ERROR: "#VS
height:5
width:50
wrap:word
%% light red color
background:c(255 150 150))
button(text:"OK"
action:toplevel#close))}
in
{Window show}
% %% NB: blocks, until OK buttom is pressed, and a surrounding thread does not help against this.
% _ = {Tk.return tk_messageBox(icon:error
% type:ok
% message:VS)}
end
end