Skip to content

Commit

Permalink
rewrote how Usage/Inputs/Outputs are handled
Browse files Browse the repository at this point in the history
  • Loading branch information
mahrud committed Sep 11, 2024
1 parent 6a17cf4 commit b653b7a
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 56 deletions.
120 changes: 67 additions & 53 deletions M2/Macaulay2/m2/document.m2
Original file line number Diff line number Diff line change
Expand Up @@ -429,37 +429,43 @@ locate DocumentTag := tag -> new FilePosition from (
-- helpers for the document function
-----------------------------------------------------------------------------

-- e.g. gb vs (codim, Ideal)
getOptionBase := key -> if options key =!= null then key else try options key#0 =!= null then key#0 else key

emptyOptionTable := new OptionTable from {}
-- TODO: this should return either an OptionTable or true, if any option is accepted
getOptionDefaultValues := method(Dispatch => Thing)
getOptionDefaultValues Symbol := x -> if instance(f := value x, Function) then getOptionDefaultValues f else emptyOptionTable
getOptionDefaultValues Thing := x -> emptyOptionTable
getOptionDefaultValues Function := f -> (
o := options f;
if o =!= null then o else emptyOptionTable)
getOptionDefaultValues Sequence := s -> (
o := options s;
if o =!= null then o else if s#?0 and instance(s#0, Function) then getOptionDefaultValues s#0 else emptyOptionTable)

processSignature := (tag, fn) -> item -> (
key := if tag =!= null then tag.Key;
getOptionDefaultValues Symbol := x -> getOptionDefaultValues value x
getOptionDefaultValues Sequence := s -> if (opts := options s) === null then (
if s#?0 and instance(s#0, Function) then getOptionDefaultValues s#0 else emptyOptionTable) else opts
getOptionDefaultValues Function := f -> if (opts := options f) === null then emptyOptionTable else opts

isType := T -> instance(T, Type)
isTypeSignature := L -> instance(L, List) and all(L, isType)

processSignature := (tag, fn) -> (type0, item) -> (
-- "inp" => ZZ => ("hypertext sequence")
-- opt => ZZ => ("hypertext sequence")
optbase := getOptionBase tag.Key;
optsymb := null; -- opt
inpname := null; -- "inp"
type := null; -- ZZ
type := null; -- ZZ or {Ideal,Module}
text := null; -- ("hypertext sequence")
fn = if (instance(key, Sequence) or instance(key, Function)) and options key =!= null then key
else if fn =!= null then fn else key;
-- 'type0' is the type in the primary document tag, e.g. Module,
-- while 'type' may be Ideal or {Ring,Ideal,Module}, etc.

-- checking for various pieces of the synopsis item
-- allow either a variable name or a visible list for rare exceptions
isVariable := y -> match(///\`[[:alnum:]']+\'///, y) or match(///(\".+\")|(\(.+\))|(\[.+\])|(\{.+\})|(<\|.+\|>)///, y);
isOptionName := y -> all({text, inpname, optsymb}, x -> x === null) and instance(y, Symbol);
isInputName := y -> all({text, inpname, optsymb}, x -> x === null) and instance(y, String) and isVariable y;
-- putting null or Nothing as input type means don't display the type deduced from the description of the method
isInputType := y -> instance(y, Type) and (
if not (type === null or y === Nothing) and type =!= y then error("type mismatch: ", toString type, " =!= ", toString y, " in documentation for ", toExternalString fn)
else if type === null or y === Nothing or type === y then true else false);
-- putting a list like {ZZ,QQ} as input type means either is acceptable
isInputType := y -> y === Nothing or isType y and isInputType {y} or isTypeSignature y and (
-- TODO: the other types are not sanity checked
type0 === null or if isMember(type0, y) then true else error("type mismatch: ",
type0, " not among types ", y, " in documentation for ", format tag));
isInputText := y -> text === null and any({String, Hypertext, List, Sequence}, T -> instance(y, T));

-- parse the chain of options
Expand All @@ -470,9 +476,9 @@ processSignature := (tag, fn) -> item -> (
if null === y then null
else if isOptionName y then optsymb = y -- option symbol, e.g Strategy
else if isInputName y then inpname = y -- input name, e.g n
else if isInputType y then type = y -- input type, e.g ZZ
else if isInputType y then type = y -- input type, e.g ZZ or {Ideal,Module}
else if isInputText y then text = y -- description, e.g {"hypertext sequence"}
else error("encountered unrecognizable synopsis item in documentation for ", toExternalString key))
else error("encountered unrecognizable synopsis item ", toString y, " in documentation for ", format tag))
) item;
if debugLevel > 1 then printerr("parsed synopsis item:\t", toExternalString (optsymb, inpname, type, text));

Expand All @@ -481,10 +487,11 @@ processSignature := (tag, fn) -> item -> (
if inpname =!= null then TT inpname,
if type =!= null and type =!= Nothing then ofClass type, -- type Nothing, treated as above
text}
else if fn =!= null then (
opts := getOptionDefaultValues fn;
if not opts#?optsymb then error("symbol ", optsymb, " is not the name of an optional argument for function ", toExternalString fn);
opttag := getPrimaryTag makeDocumentTag([fn, optsymb], Package => package tag);
else if optbase =!= null then (
opts := getOptionDefaultValues optbase;
if opts === true then opts = new OptionTable from { optsymb => null };
if not opts#?optsymb then error("symbol ", optsymb, " is not the name of an optional argument for function ", toExternalString optbase);
opttag := getPrimaryTag makeDocumentTag([optbase, optsymb], Package => package tag);
name := if tag === opttag then TT toString optsymb else TO2 { opttag, toString optsymb };
type = if type =!= null and type =!= Nothing then ofClass type else TT "..."; -- type Nothing is treated as above
maybeformat := if instance(opts#optsymb, String) then format else identity;
Expand All @@ -496,13 +503,12 @@ processSignature := (tag, fn) -> item -> (
else {TT {toString optsymb, " => ..."}};
SPAN nonnull deepSplice between_", " nonnull nonempty result)


getSignature := method(Dispatch => Thing)
getSignature Thing := x -> ({},{})
getSignature Array := x -> ({},{})
getSignature Function := x -> ({},{typicalValue x})
getSignature Sequence := x -> (
if #x > 1 and instance(x#-1, Symbol) then ({}, {}) -- it's an option ...
else (
(inputs, outputs) := (
-- putting something like OO in the key indicates a fake dispatch
x' := select(drop(toList x, 1), T -> not ancestor(Nothing, T));
-- assignment methods
Expand All @@ -515,38 +521,46 @@ getSignature Sequence := x -> (
-- for "new T from x", T is already known, so we just care about x
else if x#0 === NewFromMethod
then ( {x#2} , { typicalValue x } )
else ( x' , { typicalValue x } )))
else ( x' , { typicalValue x } ));
-- When T is not exported, its class evaluates to Symbol instead of Type
inputs = apply(inputs, T -> if instance(T, Symbol) then value T else T);
outputs = apply(outputs, T -> if instance(T, Symbol) then value T else T);
(inputs, outputs))

isOption := opt -> instance(opt, Option) and #opt == 2 and instance(opt#0, Symbol);

processUsage := (tag, fn, o) -> (
if not o.?Usage and (o.?Inputs or o.?Outputs)
then error "document: Inputs or Outputs specified, but Usage not provided";
arg := if o.?Inputs then o.Inputs else {};
out := if o.?Outputs then o.Outputs else {};
(ino, inp) := toSequence values partition(isOption, arg, {true, false});
opt := getOptionDefaultValues tag.Key;
inoh:= new HashTable from ino;
if not isSubset(keys inoh, keys opt)
then error concatenate("not among the options for ", toString fn, ": ", between_", " keys (set keys inoh - set keys opt));
ino = join(ino, sortByName (keys opt - set keys inoh));
if o.?Usage then (
(inp', out') := getSignature tag.Key;
inp' = select(inp', T -> T =!= Nothing);
out' = select(out', T -> T =!= Nothing);
-- When T is not exported, its class evaluates to Symbol instead of Type
inp' = apply(inp', T -> if instance(T, Symbol) then value T else T);
out' = apply(out', T -> if instance(T, Symbol) then value T else T);
if out' === {Thing} then out' = {}; -- not informative enough
if #inp === 0 then inp = inp';
if #out === 0 then out = out';
if #inp' =!= 0 then (
if #inp =!= #inp' then error ("mismatched number of inputs in documentation for ", format tag);
inp = apply(inp',inp,(T,v) -> T => v));
if #out' =!= 0 then (
if #out =!= #out' then error ("mismatched number of outputs in documentation for ", format tag);
out = apply(out',out,(T,v) -> T => v)));
apply((inp, out, ino), x -> apply(x, processSignature(tag, fn))))
if not o.?Usage then (
if o.?Inputs or o.?Outputs then error "document: Inputs or Outputs specified, but Usage not provided";
return ({}, {}, {}));
-- inputs, outputs, and options provided in the documentation
arguments := partition(isOption, if o.?Inputs then o.Inputs else {}, {false, true});
inputList := arguments#false;
optionList := arguments#true;
outputList := if o.?Outputs then o.Outputs else {};
optionNames := keys hashTable optionList;
-- default inputs, outputs, and options of the function or method
(inputs0, outputs0) := getSignature tag.Key;
options0 := getOptionDefaultValues tag.Key;
-- e.g. map(Module, Nothing, Matrix)
inputs0 = select( inputs0, T -> T =!= Nothing);
outputs0 = select(outputs0, T -> T =!= Nothing and T =!= Thing);
options0 = if options0 === true then optionNames else keys options0;
-- check that option names are valid
if not isSubset(optionNames, options0) then error("not among the options for ", format tag, ": ",
between_", " (optionNames - set options0));
optionList = join(optionList, sortByName (options0 - set optionNames));
optionList = apply(optionList, option -> (null, option));
--
if #inputList === 0 then inputList = inputs0;
if #outputList === 0 then outputList = outputs0;
if #inputs0 != 0 then (
if #inputList =!= #inputs0 then error ("mismatched number of inputs in documentation for ", format tag);
inputList = apply(inputs0, inputList, identity));
if #outputs0 != 0 then (
if #outputList =!= #outputs0 then error ("mismatched number of outputs in documentation for ", format tag);
outputList = apply(outputs0, outputList, identity));
apply((inputList, outputList, optionList), x -> apply(x, processSignature(tag, fn))))

-- "x" => List => { "a list of numbers" }
-- "x" => List => "a list of numbers"
Expand Down
1 change: 1 addition & 0 deletions M2/Macaulay2/m2/methods.m2
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ oftab := new HashTable from {
functionBody(true >> identity) => f -> null,
}

-- TODO: this should return either an OptionTable or true, if any option is accepted
options Function := f -> if oftab#?(fb := functionBody f) then oftab#fb f

-----------------------------------------------------------------------------
Expand Down
4 changes: 1 addition & 3 deletions M2/Macaulay2/packages/SimpleDoc.m2
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,7 @@ items = (textlines, keylinenum) -> apply(splitByIndent(textlines, false), (s, e)
desc := demark(" ", getText \ textlines_{s+1..e});
result := render(if s === e then abbr else abbr | desc, getLinenum textlines#s);
if ps#1 != "" then result = (
type := value ps#1;
if instance(type, List) then between_", " {ofClass type, result} else
if instance(type, String) then between_", " { type, result} else type => result);
if instance(type := value ps#1, String) then between_", " {type, result} else type => result);
if ps#0 != "" then result = (if match("=>", line) then value else identity) ps#0 => result;
result))

Expand Down

0 comments on commit b653b7a

Please sign in to comment.