6
$\begingroup$

NOTE: The finished code for this project can be found here, under "UPDATE": How do I alter this \$PreRead + \$PrePrint statement so it can be selectively deactivated?

Summary

I have two blocks of code, reproduced at the bottom of this post, each of which provides significant utility. The first, which automatically formats outputs as 'input = output', consists of a \$PrePrint and a \$Post statement. The second, which allows easy inline combination of math and text, consists of a \$PreRead and a \$PrePrint. I’d like to implement both simultaneously, but can’t because only one \$PrePrint statement can be active in given session.  I’d thus like to merge these into a single block of code, which contains just one \$PrePrint statement, enabling me to obtain their combined functionality, and thus easily produce outputs that combine input, output and text.

Details

The first piece of code (code block 1, below), written by Simon Rochester (Would like input and output printed on same line, w/o needing extra syntax), directs MMA to print input and output on a single line, in TraditionalForm, without the need for any added syntax. For example:

int = Integrate[x^2, x]
int/x
%/x

$\text{int}=\int x^2 \, dx=\frac{x^3}{3}$

$\frac{\text{int}}{x}=\frac{x^2}{3}$

$\frac{\%}{x}=\frac{x}{3}$

g[x_] := Sin[x]
g[Pi]

$g(x\_):=\sin(x)$

$g(\pi)=0$

a = 2

$a=2$

The second piece of code (code block 2, below), originally written by Mr. Wizard (Notebook formatting - easier descriptions for equations and results?), and which I extended with significant assistance from MB1965 (How to generalize this combined PreRead + PrePrint code?), enables me to output math and text on a single line, with minimal added syntax — I can put text before, after, or both before and after the math; I can also output text alone:

"Text before."; Integrate[x^2, x]
Integrate[x^2, x]; "Text after"
"Text before."; Integrate[x^2, x]; "Text after."
"Text only.";

$\color{blue}{\textit {Text before.}}\>\frac{x^3}{3}$

$\frac{x^3}{3} \>\>\color{blue}{\textit {Text after.}}$

$\color{blue}{\textit {Text before.}}\>\frac{x^3}{3}\>\>\color{blue}{\textit {Text after.}}$

$\color{blue}{\textit {Text only.}}$

Note that the semicolon is only needed as a separator (not a terminator), except in the "text only" case.

Objective

I’d like to implement both code blocks simultaneously, enabling me to obtain their combined functionality, e.g.:

"Letting"; int=Integrate[x^2,x]
"We find that"; int/x; ", as expected."

$\color{blue}{\textit {Letting}}\>\text{int}=\int x^2 \, dx=\frac{x^3}{3}$

$\color{blue}{\textit {We find that}}\>\frac{int}{x} = \frac{x^2}{3}\> \color{blue}{\textit {, as expected.}}$

Motivation

The rationale for this is to enable me to create, with minimal extra work, notebooks that are easily readable by non-MMA users. Thus, once the above objective is met, I can then use R. M.’s code-hiding palette (Is there a way to hide or toggle the visibility of code?) to remove all the input, leaving me with just the output alone.

Issues

The one issue I’ve noticed is that, while code block 1 will output both input and output even if there is normally no printed output, e.g.:

g[x_]:=Sin[x]

$g(\text{x$\_$}):=\sin (x)$

….code block 2 doesn’t output anything when there is no printed math output:

"Text before."; g[x_]:=Sin[x]

[No ouput]

MB1965 suggested this is because “whenever it [\$PreRead] encounters a Null return it doesn't bother to even check $PrePrint and simply returns nothing”. MB1965 further noted that, if an extra ""; "" is added after the math (where the second pair of quotes can contain text, if text after the math is desired), MMA will then print all the quoted text:

"Text before."; g[x_] := Sin[x]; ""; ""
g[x_] := Sin[x]; ""; "Text after"
"Text before"; g[x_] := Sin[x]; ""; "Text after"

$\color{blue}{\textit {Text before.}}$

$\color{blue}{\textit {Text after.}}$

$\color{blue}{\textit {Text before.}}\>\>\>\color{blue}{\textit {Text after.}}$

It would be great if I could get a fix for this corner case in the merged code.

Code block 1

$PrePrint = (TraditionalForm@HoldForm[In[line] = #] /. line -> $Line /. DownValues[In] /.
     {HoldPattern[a_ = a_] :> a,
      HoldPattern[a_ = HoldForm[a_]] :> a,
      HoldPattern[(c : (a_ = b_)) = b_] :> c,
      HoldPattern[(a_ = b_) = c_] :> HoldForm[a = b = c]
      }
    ) &;

$Post = (Replace[#, Null -> HoldForm[In[line]] /. line -> $Line /. DownValues[In]]) &;

Code block 2

$note1 = Null;
$note2 = Null;
$note3 = Null;
$PreRead = 
Replace[#, {RowBox[{note1_String?(StringMatchQ[#, "\"*\""] &), ";", 
    body__, ";", 
    note2_String?(StringMatchQ[#, "\"*\""] &)}] :> ($note1 = 
    Style[ToExpression@note1, FontFamily -> "Times", Italic, FontSize -> 15, Blue]; $note2 = 
    Style[ToExpression@note2, FontFamily -> "Times", Italic, FontSize -> 15, Blue];
   RowBox[{body}]),
 RowBox[{body__, ";", 
    note_String?(StringMatchQ[#, "\"*\""] &)}] :> ($note2 = 
    Style[ToExpression@note, FontFamily -> "Times", Italic, FontSize -> 15, Blue];
   $note1 = Null;
   RowBox[{body}]),
 RowBox[{note_String?(StringMatchQ[#, "\"*\""] &), ";", 
    body__}] :> ($note1 = Style[ToExpression@note, FontFamily -> "Times", Italic, FontSize -> 15, Blue];
   $note2 = Null;
   RowBox[{body}]),
 RowBox[{note_String?(StringMatchQ[#, "\"*\""] &), 
    ";"}] :> ($note3 = Style[ToExpression@note, FontFamily -> "Times", Italic, FontSize -> 15, Blue];
   $note2 = Null; $note1 = Null;
   RowBox[{note}]),
 e_ :> ($note1 = Null; $note2 = Null; e)}] &;
$PrePrint = Which[($note1 == Null && $note2 == Null && $note3 =!= 
   Null), # &[$note3, $note3 = Null],
($note1 =!= Null && $note2 =!= Null && $note3 == Null), # &[
 Row[{$note1, Spacer[5], Pane@#, Spacer[5], $note2}], $note1 = 
  Null, $note2 = Null],
($note1 =!= Null && $note2 =!= Null && $note3 == Null), # &[
 Row[{$note1, Spacer[5], Pane@#, Spacer[5], $note2}], $note1 = 
  Null, $note2 = Null],
($note1 =!= Null && $note2 === Null && $note3 == Null), # &[
 Row[{$note1, Spacer[5], Pane@#}], $note1 = Null],
($note1 === Null && $note2 =!= Null && $note3 == Null), # &[
 Row[{Pane@#, Spacer[5], $note2}], $note2 = Null], True, #] &;
$\endgroup$

1 Answer 1

6
$\begingroup$

I took the liberty of partitioning your code into functions for each formatting step.

For part 2, you simply need to edit the $PreRead so that it never actually sees Null. Here's a function that does that:

boxExpr[body_] :=
  RowBox@{"Replace", "[", "\"thisIsJustATag\"", ";", body, ",", 
    "Null", "->", "\"\"", "]"};

The "thisIsJustATag" there will make more sense in a moment.

Then I pulled out the note extraction task and used boxExpr everywhere there was RowBox[{body}]. Note that I'm using the styleNote function I defined in my suggestion at the bottom. To deal with issue (2) mentioned in the comments we also apply boxExpr in the no-note case.

extractNotes[boxes_] := 
  Replace[boxes, {RowBox[{note1_String?(StringMatchQ[#, "\"*\""] &), 
       ";", body__, ";", 
       note2_String?(StringMatchQ[#, "\"*\""] &)}] :> ($note1 = 
       styleNote[note1, "Before"]; $note2 = 
       styleNote[note2, "After"];
      boxExpr@body), 
    RowBox[{body__, ";", 
       note_String?(StringMatchQ[#, "\"*\""] &)}] :> ($note2 = 
       styleNote[note, "After"];
      $note1 = Null;
      boxExpr@body), 
    RowBox[{note_String?(StringMatchQ[#, "\"*\""] &), ";", 
       body__}] :> ($note1 = styleNote[note, "After"];
      $note2 = Null;
      boxExpr@body),
    RowBox[{note_String?(StringMatchQ[#, "\"*\""] &), 
       ";"}] :> ($note3 = styleNote[note, "Neither"];
      $note2 = Null; $note1 = Null;
      note), 
    e_ :> ($note1 = Null; $note2 = Null; boxExpr@e)}];

Next I tackled joining the two parts. Easiest to do via a two step process. First we apply the Part 1 code you supplied plus a tweak to remove the Replace expression we added in boxExpr (this is where the tag is important):

applyFormatting[out_] :=
  With[{line = $Line},
   HoldForm[In[line] = $placeHolder] /.

      DownValues[In] /. {
      $placeHolder -> out,
      HoldPattern[
        Replace[CompoundExpression["thisIsJustATag", expr_], 
         Null -> ""]] :> expr
      }
    /. {
     HoldPattern[a_ = ""] :> a,
     HoldPattern[a_ = a_] :> a,
     HoldPattern[a_ = HoldForm[a_]] :> a,
     HoldPattern[(c : (a_ = b_)) = b_] :> c,
     HoldPattern[(a_ = b_) = c_] :> HoldForm[a = b = c]
     }
   ];

And then I pulled out the note formatting function and cleaned that up a little (using Switch is easier than Which here I think):

addNotes[formatted_] :=
  TraditionalForm@Switch[{$note1, $note2, $note3},
   {Null, Null, Except@Null},
   With[{r = $note3}, $note3 = Null; r],
   {Except@Null, Except@Null, _},
   With[{r1 = $note1, r2 = $note2}, $note1 = $note2 = Null; 
    Row[{r1, formatted, r2}, Spacer[5]]
    ],
   {Except@Null, _, _},
   With[{r = $note1}, $note1 = Null;
    Row[{r, formatted}, Spacer[5]]
    ],
   {_, Except@Null, _},
   With[{r = $note2}, $note2 = Null;
    Row[{formatted, r}, Spacer[5]]
    ],
   _,
   formatted
   ];

And then all we need is this:

$PreRead = extractNotes;
$PrePrint = addNotes@*applyFormatting;

to get the desired result.

Suggestion

To make your life easier I'd suggest adding a store of text styles like:

$outputStyles =
  <|
   "Default" -> {
     Blue,
     15,
     Italic,
     FontFamily -> "Times"
     },
   "Before" -> {
     Blue,
     15,
     Italic,
     FontFamily -> "Times"
     },
   "After" -> {
     Blue,
     15,
     Italic,
     FontFamily -> "Times"
     }
   |>;

and a styling function like:

styleNote[note_, style_] :=
  Style[ToExpression@note, 
   Sequence @@ Lookup[$outputStyles, style, $outputStyles["Default"]]];

And then you can replace all of your note styling calls with a simple styleNote calls which will allow you to change how you do the styling without changing your $PreRead at all.

For example you can change the color for your after note text like this:

$outputStyles["After"] = $outputStyles["After"] /. Blue -> Red

No need to edit the $PreRead function at all this way.

All together

For those who don't want to copy all the pieces in, this is all the formatting code as I have it:

$note1 = Null;
$note2 = Null;
$note3 = Null;

$outputStyles =
  <|
   "Default" -> {
     Blue,
     15,
     Italic,
     FontFamily -> "Times"
     },
   "Before" -> {
     Blue,
     15,
     Italic,
     FontFamily -> "Times"
     },
   "After" -> {
     Blue,
     15,
     Italic,
     FontFamily -> "Times"
     }
   |>;

boxExpr[body_] :=

  RowBox@{"Replace", "[", "\"thisIsJustATag\"", ";", body, ",", 
    "Null", "->", "\"\"", "]"};
styleNote[note_, style_] :=

  Style[ToExpression@note, 
   Sequence @@ Lookup[$outputStyles, style, $outputStyles["Default"]]];

extractNotes[boxes_] := 
  Replace[boxes, {RowBox[{note1_String?(StringMatchQ[#, "\"*\""] &), 
       ";", body__, ";", 
       note2_String?(StringMatchQ[#, "\"*\""] &)}] :> ($note1 = 
       styleNote[note1, "Before"]; $note2 = 
       styleNote[note2, "After"];
      boxExpr@body), 
    RowBox[{body__, ";", 
       note_String?(StringMatchQ[#, "\"*\""] &)}] :> ($note2 = 
       styleNote[note, "After"];
      $note1 = Null;
      boxExpr@body), 
    RowBox[{note_String?(StringMatchQ[#, "\"*\""] &), ";", 
       body__}] :> ($note1 = styleNote[note, "After"];
      $note2 = Null;
      boxExpr@body),
    RowBox[{note_String?(StringMatchQ[#, "\"*\""] &), 
       ";"}] :> ($note3 = styleNote[note, "Neither"];
      $note2 = Null; $note1 = Null;
      note), 
    e_ :> ($note1 = Null; $note2 = Null; boxExpr@e)}];

applyFormatting[out_] :=
  With[{line = $Line},
   HoldForm[In[line] = $placeHolder] /.

      DownValues[In] /. {
      $placeHolder -> out,
      HoldPattern[
        Replace[CompoundExpression["thisIsJustATag", expr_], 
         Null -> ""]] :> expr
      }
    /. {
     HoldPattern[a_ = ""] :> a,
     HoldPattern[a_ = a_] :> a,
     HoldPattern[a_ = HoldForm[a_]] :> a,
     HoldPattern[(c : (a_ = b_)) = b_] :> c,
     HoldPattern[(a_ = b_) = c_] :> HoldForm[a = b = c]
     }
   ];
addNotes[formatted_] :=
  TraditionalForm@Switch[{$note1, $note2, $note3},
   {Null, Null, Except@Null},
   With[{r = $note3}, $note3 = Null; r],
   {Except@Null, Except@Null, _},
   With[{r1 = $note1, r2 = $note2}, $note1 = $note2 = Null; 
    Row[{r1, formatted, r2}, Spacer[5]]
    ],
   {Except@Null, _, _},
   With[{r = $note1}, $note1 = Null;
    Row[{r, formatted}, Spacer[5]]
    ],
   {_, Except@Null, _},
   With[{r = $note2}, $note2 = Null;
    Row[{formatted, r}, Spacer[5]]
    ],
   _,
   formatted
   ];

$PreRead = extractNotes;
$PrePrint = addNotes@*applyFormatting;
$\endgroup$
9
  • $\begingroup$ Thanks for taking the time to go through this! I tried your "all together" code block on a variety of inputs. It worked on all of them, with four exceptions: (1) The output of "Text alone"; is unformatted. (2) g[x_] := Sin[x] gives no output. (3) "Text before."; g[x_] := Sin[x] and g[x_] := Sin[x]; "Text after." both give the text properly, but format the math with an extra set of parentheses and an extra equals sign: "(g(x_) := sin(x)) =" . (4) ``"Text before."; g[x_] := Sin[x]; "Text after." gives only "(g(x_) := sin(x)) =" (no text). $\endgroup$
    – theorist
    Commented Jan 3, 2017 at 6:23
  • $\begingroup$ ....in addition, the math no longer formats in TraditionalForm, even though you have TraditionalForm@HoldForm[In[line] = $placeHolder] in your code. I mention this principally as a curiosity, since I can easily correct it by setting Preferences->Evaluation->Output to TraditionalForm. $\endgroup$
    – theorist
    Commented Jan 3, 2017 at 6:26
  • 1
    $\begingroup$ (1) and (3) are little mistakes in the code. Easy to patch. (just did). The rest of the stuff I'll have to look at more. Just gave a cursory look at the code you posted to combine the two. $\endgroup$
    – b3m2a1
    Commented Jan 3, 2017 at 6:27
  • $\begingroup$ I also just noticed a vertical offset between the text and the math that's been present since Mr. Wizard's orig. code. When there's both an = sign and a fraction in the output, the text is usu. vertically offset higher than the math (i.e., not in line with the = sign). E.g., in the output of "Text before."; a = 3/x , the text is higher, but in "Text before."; 3/x and "Text before."; test = {{f1, f2}, {g1, g2}} it's not. The exception is with sums, where the text is lower, e.g., "Text before."; sum = Sum[1/p^2, {p, 1, Infinity}]. Any idea what's causing this, and how to correct it? $\endgroup$
    – theorist
    Commented Jan 3, 2017 at 6:33
  • 1
    $\begingroup$ I can now confirm that MB1965's code solves all the issues I mentioned, fixing not only 1 – 4, but also the vertical text offset. Further, quitting and restarting the program fixed the issue I had with TraditionalForm. I had been resetting by quitting the kernel, changing output form in global prefs., and reactivating the code. But it seems there were some odd settings on my system that quitting the kernel didn't clear, and that were only cleared by quitting the program. Now I get the same behavior as MB1965 sees, where it prints TradForm even if output is set to StdForm in global prefs. $\endgroup$
    – theorist
    Commented Jan 3, 2017 at 18:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.