File: examples.m2

package info (click to toggle)
macaulay2 1.21%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 133,096 kB
  • sloc: cpp: 110,377; ansic: 16,306; javascript: 4,193; makefile: 3,821; sh: 3,580; lisp: 764; yacc: 590; xml: 177; python: 140; perl: 114; lex: 65; awk: 3
file content (260 lines) | stat: -rw-r--r-- 13,553 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
-----------------------------------------------------------------------------
-- Methods for processing and accessing examples from the documentation
-----------------------------------------------------------------------------
-* Exported:
 * EXAMPLE
 * capture
 * examples
 *-

needs "hypertext.m2"
needs "run.m2"

processExamplesStrict = true

-----------------------------------------------------------------------------
-- local utilities
-----------------------------------------------------------------------------

M2outputRE       = "\n+(?=i+[1-9][0-9]* : )"
M2outputHash     = "-- -*- M2-comint -*- hash: "
separateM2output = str -> (
    L := separate(M2outputRE, "\n" | replace("\n+\\Z", "", str));
    if #L<=1 then L else drop(L,1))

trimlines := L -> apply(L, x ->
    if instance(x, String) then (
	s := lines x;
	r := if s#?0 then demark_newline prepend(replace("^[[:space:]]+", "", s#0), drop(s, 1)) else x;
	if #r > 0 then r)
    else x)

-----------------------------------------------------------------------------
-- EXAMPLE
-----------------------------------------------------------------------------

makeExampleItem = method()
-- TODO: can this be handled with a NewFromMethod?
makeExampleItem PRE    := p -> flatten apply(toList p, s -> PRE \ M2CODE \ separateM2output s)
makeExampleItem String := s -> ExampleItem s

-- allows canned examples with EXAMPLE PRE "..."
EXAMPLE = method(Dispatch => Thing)
EXAMPLE PRE         :=
EXAMPLE String      := x -> EXAMPLE {x}
EXAMPLE VisibleList := x -> (
    L := flatten \\ makeExampleItem \ nonnull trimlines toList x;
    if #L == 0 then error "EXAMPLE: empty list of examples encountered";
    TABLE flatten {"class" => "examples", apply(L, item -> TR TD item)})

-----------------------------------------------------------------------------
-- capture
-----------------------------------------------------------------------------
-- TODO: move to capture.m2

-- TODO: the output format is provisional
-- TODO: doesn't capture stderr
capture' := capture
capture = method(Options => { UserMode => true, PackageExports => null })
capture Net    := opts -> s -> capture(toString s,       opts)
capture List   := opts -> s -> capture(demark_newline s, opts)
-- TODO: do this in interp.dd instead
-- TODO: alternatively, change the setup to do this in a clean thread
capture String := opts -> s -> if opts.UserMode then capture' s else (
    -- output is (Boolean, String) => (Err?, Output)
    -- TODO: this should eventually be unnecessary
    oldMutableVars := new MutableHashTable;
    scan(flatten apply(loadedPackages, pkg -> pkg#"exported mutable symbols"), symb -> oldMutableVars#symb = value symb);
    -* see run.m2 for details of defaultMode, argumentMode, etc. *-
    -- TODO: somehow use SetUlimit, GCMAXHEAP, GCSTATS, GCVERBOSE,
    --       ArgInt, ArgQ, ArgNoReadline, ArgNoSetup, and ArgNoThreads
    argmode := if 0 < argumentMode & InvertArgs then defaultMode ^^ argumentMode else argumentMode;
    hasmode := m -> argmode & m == m;
    pushvar(symbol randomSeed, if hasmode ArgNoRandomize then 0 else randomSeed);
    -- TODO: these two are overridden in interp.dd at the moment
    --if hasmode ArgStop        then (stopIfError, debuggingMode) = (true, false);
    --if hasmode ArgNoDebug     then debuggingMode = false;
    if hasmode ArgPrintWidth  then printWidth = ArgPrintWidthN;
    if hasmode ArgNoBacktrace then backtrace = false;
    if hasmode ArgNotify      then notify = true;

    oldPrivateDictionary := User#"private dictionary";
    User#"private dictionary" = new Dictionary;
    -- FIXME: why does OutputDictionary lose its Attribute if it isn't saved this way?
    pushvar(symbol OutputDictionary, new Dictionary);
    dictionaryPath = {
	currentPackage.Dictionary,
	Core.Dictionary,
	OutputDictionary,
	PackageDictionary};
    if not hasmode ArgNoPreload then
    scan(Core#"pre-installed packages", needsPackage);
    if opts.PackageExports =!= null then (
	 if instance(opts.PackageExports, String) then needsPackage opts.PackageExports
	 else if instance(opts.PackageExports, Package) then needsPackage toString opts.PackageExports
	 else if instance(opts.PackageExports, List) then needsPackage \ opts.PackageExports
	 else error ("expected PackageExports option value (",toString opts.PackageExports,") to be a string or a list of strings"));
    --shallow copy, but we only need to remember which things started with attributes
    oldAttributes := copy Attributes;
    -- TODO: is this still necessary? If so, add a test in tests/normal/capture.m2
    -- dictionaryPath = prepend(oldPrivateDictionary,      dictionaryPath); -- this is necessary mainly due to T from degreesMonoid
    dictionaryPath = prepend(User#"private dictionary", dictionaryPath); -- this is necessary mainly due to indeterminates.m2
    currentPackage = User;

    ret := capture' s;
    collectGarbage();

    --Without the toSequence {v}, if v is a Sequence, hasAnAttribute breaks
    scan(value \ values User#"private dictionary", v ->
        if hasAnAttribute toSequence {v} and not oldAttributes#?v
        then remove(Attributes,v));
    -- null out all symbols in the private dictionary, otherwise those values leak
    -- See bug #2330 for details.
    scan(values User#"private dictionary", s -> (if mutable s then s <- null));
    User#"private dictionary" = oldPrivateDictionary;
    -- null out the symbols in the OutputDictionary as well
    scan(values OutputDictionary, s -> (if mutable s then s <- null));
    popvar symbol OutputDictionary;
    -- TODO: this should eventually be unnecessary
    scan(keys oldMutableVars, symb -> symb <- oldMutableVars#symb);
    popvar symbol randomSeed;
    ret)
protect symbol capture

-- returns false if the inputs or the package are not known to behave well with capture
-- this is also used in testing.m2, where isTest is set to true.
isCapturable = (inputs, pkg, isTest) -> (
    -- argumentMode is mainly used by ctest to select M2 subprocess arguments,
    -- or whether capture should be avoided; see packages/CMakeLists.txt
    -- alternatively, no-capture-flag can be used with an example or test
    if argumentMode & NoCapture =!= 0 or match("no-capture-flag", inputs) then return false;
    -- strip commented segments first
    inputs = replace("--.*$", "",       inputs);
    inputs = replace("-\\*.*?\\*-", "", inputs);
    -- TODO: remove this when the effects of capture on other packages is reviewed
    (isTest or match({"FirstPackage", "Macaulay2Doc"},            pkg#"pkgname"))
    and not match({"MultiprojectiveVarieties", "EngineTests","ThreadedGB","RunExternalM2","SpecialFanoFourfolds"}, pkg#"pkgname")
    and not (match({"Cremona"}, pkg#"pkgname") and version#"pointer size" == 4)
    -- FIXME: these are workarounds to prevent bugs, in order of priority for being fixed:
    and not match("(gbTrace|NAGtrace)",                       inputs) -- cerr/cout directly from engine isn't captured
    and not match("(notify|stopIfError|debuggingMode)",       inputs) -- stopIfError and debuggingMode may be fixable
    and not match("(alarm|exec|exit|quit|restart|run)\\b",    inputs) -- these commands interrupt the interpreter
    and not match("(capture|read|input|load|needs)\\b",       inputs) -- these commands hide undesirable functions
    and not match("([Cc]ommand|fork|schedule|thread|Task)",   inputs) -- remove when threads work more predictably
    and not match("(temporaryFileName)",                      inputs) -- this is sometimes bug prone
    and not match("(addHook|export|newPackage)",              inputs) -- these commands have permanent effects
    and not match("(installMethod|installAssignmentMethod)",  inputs) -- same as above
    and not match("(Global.*Hook|add.*Function|Echo|Print)",  inputs) -- same as above
    and not match("(importFrom|exportFrom)",                  inputs) -- currently capture tries to clear all symbols created, these break it
    )

-----------------------------------------------------------------------------
-- extract examples
-----------------------------------------------------------------------------

extractExamplesLoop := method(Dispatch => Thing)
extractExamplesLoop Thing       := x -> ()
extractExamplesLoop Sequence    :=
extractExamplesLoop Hypertext   := x -> deepSplice apply(toSequence x, extractExamplesLoop)
extractExamplesLoop ExampleItem := toSequence

extractExamples = docBody -> (
    ex := toList extractExamplesLoop docBody;
    -- don't convert "ex" on the next line to a sequence,
    -- because the hash code for caching example outputs will change
    if #ex > 0 then currentPackage#"example inputs"#(format currentDocumentTag) = ex;
    docBody)

-----------------------------------------------------------------------------
-- examples: get a list of examples in a documentation node
-----------------------------------------------------------------------------

examples = method(Dispatch => Thing)
examples Hypertext := dom -> raise(stack extractExamplesLoop dom, -1)
examples Thing     := key -> (
    rawdoc := fetchAnyRawDocumentation makeDocumentTag key;
    if rawdoc =!= null and rawdoc.?Description then examples DIV{rawdoc.Description})

-----------------------------------------------------------------------------
-- storeExampleOutput
-----------------------------------------------------------------------------

getExampleOutputFilename := (pkg, fkey) -> (
    if pkg#?"package prefix" and pkg#"package prefix" =!= null then (
	packageLayout := detectCurrentLayout pkg#"package prefix";
	if packageLayout === null then error "internal error: package layout not detected";
	pkg#"package prefix" | replace("PKG", pkg#"pkgname", Layout#packageLayout#"packageexampleoutput") | toFilename fkey | ".out")
    else error "internal error: package prefix is undefined")

getExampleOutput := (pkg, fkey) -> (
    -- TODO: only get from cache if the hash hasn't changed
    if pkg#"example results"#?fkey then return pkg#"example results"#fkey;
    verboseLog := if debugLevel > 1 then printerr else identity;
    filename := getExampleOutputFilename(pkg, fkey);
    output := if fileExists filename
    then ( verboseLog("info: reading cached example results from ", filename); get filename )
    else if width (ex := examples fkey) =!= 0
    then ( verboseLog("info: capturing example results on-demand"); last capture(ex, UserMode => false, PackageExports => pkg) );
    pkg#"example results"#fkey = if output === null then {} else separateM2output output)

-- used in installPackage.m2
-- TODO: store in a database instead
storeExampleOutput = (pkg, fkey, outf, verboseLog) -> (
    verboseLog("storing example results in ", minimizeFilename outf);
    if fileExists outf then (
	outstr := reproduciblePaths get outf;
	outf << outstr << close;
	pkg#"example results"#fkey = separateM2output outstr)
    else verboseLog("warning: missing file ", outf));

-- used in installPackage.m2
-- TODO: reduce the inputs to this function
captureExampleOutput = (desc, inputs, pkg, inf, outf, errf, data, inputhash, changeFunc, usermode) -> (
    stdio << flush; -- just in case previous timing information hasn't been flushed yet
    -- try capturing in the same process
    if isCapturable(inputs, pkg, false) then (
	desc = concatenate(desc, 62 - #desc);
	stderr << commentize pad("capturing " | desc, 72) << flush; -- the timing info will appear at the end
	(err, output) := capture(inputs, UserMode => false);
	alarm 0;			     -- cancel any alarms that were set
	if err then printerr "capture failed; retrying ..."
	else (outf << M2outputHash << inputhash << endl << output << close;
	    return true));
    -- fallback to using an external process
    stderr << commentize pad("making " | desc, 72) << flush;
    inf << replace("-\\* no-capture-flag \\*-", "", inputs) << endl << close;
    r := runFile(inf, inputhash, outf, errf, pkg, changeFunc, usermode, data);
    if r then removeFile inf;
    r)

-----------------------------------------------------------------------------
-- process examples
-----------------------------------------------------------------------------
-- TODO: make this reentrant
-- TODO: avoid the issue of extra indented lines being skipped in SimpleDoc
-- a hacky fix is dumping any remaining example results along with the last example
-- a better fix probably requires rethinking the ExampleItem mechanism

local currentExampleKey
local currentExampleCounter
local currentExampleResults

processExamplesLoop = method(Dispatch => Thing)
processExamplesLoop Thing       := identity
processExamplesLoop Sequence    :=
processExamplesLoop Hypertext   := x -> apply(x, processExamplesLoop)
processExamplesLoop ExampleItem := x -> (
    result := if currentExampleResults#?currentExampleCounter then PRE M2CODE currentExampleResults#currentExampleCounter else (
	if #currentExampleResults === currentExampleCounter then (
	    if processExamplesStrict
	    then error("example results terminate prematurely: ", toString currentExampleKey)
	    else printerr("warning: example results terminate prematurely: ", toString currentExampleKey));
	PRE concatenate("i", toString (currentExampleCounter + 1), " : -- example results terminated prematurely"));
    currentExampleCounter = currentExampleCounter + 1;
    result)

processExamples = (pkg, fkey, docBody) -> (
    currentExampleKey = fkey;
    currentExampleCounter = 0;
    currentExampleResults = getExampleOutput(pkg, fkey);
    if #currentExampleResults > 0 then processExamplesLoop docBody else docBody)