I don't think you can avoid parsing or string conversion in one way or another - at the end you need strings, and you start with symbols. Either you somehow reuse MakeBoxes
, or you have to deal with strings. Dragging my code around: the following simple box-making function is based on the Mathematica parser posted here (my second post there, at the bottom of the page):
Clear[toBoxes];
toBoxes[expr_] :=
First[parse[tokenize[ToString@FullForm[expr]]] //. {
head_String[elem_] :> RowBox[{head, "[", elem, "]"}],
head_String[elems___] :> RowBox[{head, "[", RowBox[Riffle[{elems}, ","]], "]"}]}]
If you don't want to parse but don't mind ToString
, then a slight variation of the above will do:
toBoxesAlt[expr_] :=
expr /. s_Symbol :> ToString[s] //. {
head_String[elem_] :> RowBox[{head, "[", elem, "]"}],
head_String[elems___] :> RowBox[{head, "[", RowBox[Riffle[{elems}, ","]], "]"}]}
Note that this last function does not involve any parsing.Then, we need:
Clear[MakeBoxesStopAlt];
MakeBoxesStopAlt /: MakeBoxes[MakeBoxesStopAlt[expr_], form_] := toBoxes[expr]
For example:
In[327]:= MakeBoxesStopAlt[Graphics[Disk[]]]
Out[327]= Graphics[Disk[List[0, 0]]]
You may want to re-implement the parser if my implementation looks too complicated, although mine is rather efficient.
EDIT
Here is a very simplistic and probably slow approach to parsing: the function tokenize
is the same as before, and I will repost it here for convenience:
tokenize[code_String] :=
Module[{n = 0, tokenrules},
tokenrules = {"[" :> {"Open", ++n}, "]" :> {"Close", n--},
Whitespace | "" ~~ "," ~~ Whitespace | ""};
DeleteCases[StringSplit[code, tokenrules], "", Infinity]];
Here is the parsing function:
parseSimple[tokenized_] :=
First[tokenized //. {left___,
Shortest[
PatternSequence[h_, {"Open", n_}, elems___, {"Close", n_}]], right___} :>
{left, h[elems], right}];
You may use it in place of parse
, and these two functions then form a self-contained solution for the parser.
The same comment as for my answer to your previous question is in order: if you want to handle /disallow expression evaluation, add appropriate attributes and Unevaluated
wrappers where needed.
EDIT2
Here is a version of makeBoxes that does not involve parsing, does not leak evaluation and does handle nested heads correctly (at least for some simple tests):
Clear[handleElems];
handleElems[] := Sequence[];
handleElems[el_] := el;
handleElems[els__] := RowBox[Riffle[{els}, ","]];
ClearAll[makeBoxes];
SetAttributes[makeBoxes, HoldAllComplete];
makeBoxes[ex_] :=
Block[{makeBoxes},
SetAttributes[makeBoxes, HoldAllComplete];
makeBoxes[expr_ /;!FreeQ[Unevaluated[expr],
s_ /; AtomQ[Unevaluated[s]] && ! StringQ[Unevaluated[s]]]] :=
makeBoxes[#] &@(Unevaluated[expr] /.
s_ /; AtomQ[Unevaluated[s] && ! StringQ[Unevaluated[s]]] :>
ToString[Unevaluated[s]]);
makeBoxes[a_ /; AtomQ[Unevaluated[a]]] := a;
makeBoxes[expr_] /; MatchQ[expr, h_String[___]] :=
expr //. {
(h : ("Rule" | "RuleDelayed"))[l_, r_] :>
RowBox[{l, h /. {
"Rule" -> "\[Rule]",
"RuleDelayed" -> "\[RuleDelayed]"
}, r}],
"List"[elems___] :> RowBox[{"{", handleElems[elems], "}"}],
head_String[elems___] :> RowBox[{head, "[", handleElems[elems], "]"}]
};
makeBoxes[expr_] :=
RowBox[{makeBoxes[#] &@Head[expr], "[",
handleElems @@ (makeBoxes @@@ expr), "]"}];
makeBoxes @@ (HoldComplete[ex] /. s_String :>
With[{eval = StringJoin["\"", s, "\""]}, eval /; True])
];
Example of use:
In[228]:= a=1;b=2;c = 3;
In[229]:= makeBoxes[a:>b]
Out[229]= RowBox[{a,:>,b}]
In[230]:= makeBoxes[a->b]
Out[230]= RowBox[{a,->,b}]
In[231]:= makeBoxes[{a,{b,c}}]
Out[231]= RowBox[{{,RowBox[{a,,,RowBox[{{,RowBox[{b,,,c}],}}]}],}}]
In[232]:= makeBoxes[a[b][c]]
Out[232]= RowBox[{RowBox[{a,[,b,]}],[,c,]}]
In[233]:= makeBoxes[a[b[e[],f[]],c[g[],h[]]][x,y]]
Out[233]= RowBox[{RowBox[{a,[,RowBox[{RowBox[{b,[,RowBox[{RowBox[{e,
[,]}],,,RowBox[{f,[,]}]}],]}],,,RowBox[{c,[,RowBox[{RowBox[{g,[,]}],,,
RowBox[{h,[,]}]}],]}]}],]}],[,RowBox[{x,,,y}],]}]
In all cases tested, the output is the same as that of MakeBoxes
.