22

I am trying to create a Mathematica/shell syntax hybrid. I would like the following command

wget -qO- "http://google.com/space line
break\"" $var

to be interpreted as the following.

wget["-qO-","http://google.com/line
break\"",var]

Notice there are both line breaks and escaped quotes in the command.

I will add a bounty soon. I'm not the best at Regular Expressions but will try it myself.

William
  • 7,595
  • 2
  • 22
  • 70
  • 2
    How is the "command" entered and stored as a Mathematica object? Is it in a FormBox or plain String or...? – Jens May 09 '16 at 05:05
  • 5
    "Downvoters/closers please leave comments." How about, what have you tried? You can't protect yourself against this criticism by saying that you haven't tried anything. The last paragraph gives me the feeling that you know that it's not a good question, and you are making excuses for it instead of fixing it. (No downvote from me though.) – C. E. May 09 '16 at 05:14
  • @Jens plain string. – William May 11 '16 at 00:38
  • @William Would something similar to my answer of "Programmability of the Natural Language Interface" work for you? – Anton Antonov May 11 '16 at 20:18
  • 1
    @AntonAntonov The proper way to do this would be to use something like ANTLR which I believe is similar to what you have posted. I am kinda hopeful there is an easier way. I believe I could make the above work if it wasn't for the escaped quotes that make the regular expression rather difficult. I think look behinds might work although I am not good at regular expressions. Would you like me to vote reopen that question? – William May 11 '16 at 20:37
  • @William Does the parsing and interpretation happen in Mathematica or in some other environment? (It seems it is latter.) – Anton Antonov May 11 '16 at 20:41
  • @AntonAntonov I'm sorry I don't understand you question. Do you mean does ANTLR work in other environment. If you asking what I would prefer then yes I would prefer it to work in Mathematica opposed to another environment. – William May 11 '16 at 20:44
  • @William Your answer shows that you understood my question correctly. Thanks! – Anton Antonov May 11 '16 at 20:47
  • @C.E. I have working example now posted although I have not received any up votes yet. – William May 12 '16 at 20:08

2 Answers2

23

First of all, I agree, as OP mentioned in his comment, ANTLR is one of the proper ways to go.

Now for this specific task, it might be easier to just compose a parser in the "dirty" way, except we don't have to go so far to regex. In my opinion Mathematica's StringExpression is much more powerful and very suitable for the job.

All we have to do is (as OP already did in his answer) to write a parser for CellEvaluationFunction.

However one thing should be take care of when creating the Cell:

The shell emulator is going to process customized commands, which are unlikely to be similar to Mathematica's syntax, so the created Cell should not involve any of the Box typesetting system. One way to ensure this is to create the Cell as Cell[ "", ... ] instead of Cell[ BoxData[""], ... ] (just like what happens when you convert a Cell to the "Raw InputForm"). That case the CellEvaluationFunction receives input as a raw string rather than Boxes, so we can avoid lots of unnecessary work.

One simple parser could be like the following:

Clear[shellEmu`interpretRules, shellEmu`stringMismatchMsg, shellEmu`parser]

shellEmu`interpretRules = {
            str_String /; 
                    StringMatchQ[str, "$" ~~ LetterCharacter ~~ WordCharacter ...] :> ToExpression[StringDrop[str, 1]],
            str_String /;
                    StringMatchQ[str, NumberString]                                :> ToExpression[str]
                          };

shellEmu`stringMismatchMsg[strMarkerPos_] :=
    Failure["OddQuotations", <|
            "MessageTemplate"   -> StringTemplate["Number of quotation markers should be even and positive rather than `Number`"],
            "MessageParameters" -> <|"Number" -> Length[strMarkerPos]|>
                             |>]

shellEmu`parser[inStr_String, form_] :=
    Module[{
               workStr = StringJoin["< ", inStr, " >"],
               strLen,
               strMarkerPos,
               strMarkerPosPart, nonstrMarkerPosPart,
               posMergFunc = Function[pos, Interval @@ pos // List @@ # &],
               res
           },
           strLen           = StringLength@workStr;
           strMarkerPos     = StringPosition[workStr, #][[;; , -1]] & /@ {"\"", "\\\""} // Complement @@ # &;
           strMarkerPosPart = Partition[strMarkerPos, 2, 2, {1, 1}, {}];
           If[Length[strMarkerPos] != 0 && Length[strMarkerPosPart[[-1]]] != 2,
                   Return@shellEmu`stringMismatchMsg[strMarkerPos]
             ];
           strMarkerPosPart    = strMarkerPosPart // posMergFunc;
           nonstrMarkerPosPart = {0, Sequence @@ strMarkerPosPart, strLen + 1} // Flatten // (Partition[#, 2] + {1, -1}) & // Transpose // posMergFunc;
           res = StringTake[workStr, #] & /@ {nonstrMarkerPosPart, strMarkerPosPart};
           res //
               RightComposition[
                   MapAt[ToExpression /@ # &, #, 2] &,
                   MapAt[StringSplit[#, Whitespace] & /@ # &, #, 1] &,
                   Riffle @@ # &,
                   Flatten, #[[2 ;; -2]] &,
                   MapAt[ToExpression, #, 1] &,
                   # /. shellEmu`interpretRules &,
                   #[[1]] @@ Rest[#] &
                   ]
          ]

With that we can now create our ShellEmu Cell as following:

Cell["", "ShellEmu",
        Evaluatable -> True,
        CellEvaluationFunction -> shellEmu`parser,

        Background -> Hue[0.09, 0.41, 0.33],

        CellMargins -> {{50, 0}, {0, 0}},
        CellFrame -> {{False, False}, {5, 5}},
        CellFrameMargins -> {{10, 10}, {10, 10}},
        CellFrameColor -> Hue[0.09, 0.41, 0.56],

        FontFamily -> "Consolas",
        FontColor -> GrayLevel[0.95],
        FontWeight -> Bold,
        Hyphenation -> False,
        FrontEnd`AutoQuoteCharacters -> {},
        FrontEnd`PasteAutoQuoteCharacters -> {}
        ] // CellPrint

ShellEmu 1

Note for a clearer evidence that the command has been correctly parsed, I manually copied the output as an "Input"-style Cell with syntax highlight.

And mismatch of quotation markers will cause a failure:

ShellEmu failure

Now obviously CellPrint is too cumbersome, so we're going to get the job done more automatically.

By defining a new style in the stylesheet as following

Cell[StyleData["ShellEmu"],
      CellFrame                -> {{False, False}, {5, 5}},
      CellMargins              -> {{50, 0}, {0, 0}},
      Evaluatable              -> True,
      CellEvaluationFunction   -> shellEmu`parser,
      GeneratedCell            -> True,
      CellAutoOverwrite        -> True,
      CellFrameMargins         -> {{10, 10}, {10, 10}},
      CellFrameColor           -> Hue[0.09, 0.41, 0.56],
      Hyphenation              -> False,
      AutoQuoteCharacters      -> {},
      PasteAutoQuoteCharacters -> {},
      MenuCommandKey           -> "L",
      FontFamily               -> "Consolas",
      FontWeight               -> Bold,
      FontColor                -> GrayLevel[0.95],
      Background               -> Hue[0.09, 0.41, 0.33]]

we should be able to create "ShellEmu" Cell simply by a shortcut Alt+L.

We can define functions matching the parsed result, say:

Clear[plot]
plot[f_, var_, "from", start_, "to", end_] :=
    Module[{interm},
        Echo[Inactive[plot][f, var, "from", start, "to", end] // FullForm];
        interm = 
            Inactive[Plot][ToExpression@f, ToExpression /@ {var, start, end}];
        Echo[interm // FullForm];
        Activate[interm]
        ]

ShellEmu 2

Silvia
  • 27,556
  • 3
  • 84
  • 164
9

I extended the original question to support piping like the following.

wget -qO- "http://google.com" // cat

and it outputs the following.

cat[][wget[-qO-,http://google.com][]]

To get the following.

CellPrint@
  Cell[BoxData[""], "Input", Evaluatable -> True, 
   CellEvaluationFunction -> 
    Function[
     Module[{t}, 
      t = List@
        ReplaceRepeated[NotebookRead[EvaluationCell[]][[1, 1]], 
         RowBox[{x__}] :> x];
      t = 
       Map[Module[{t = #, a, addbacksemicolon = ""}, 
          If[t[[-1]] == ";", addbacksemicolon = ";";
           t = Delete[t, -1]];

          If[Length@t >= 2 && 
            StringMatchQ[t[[1]], 
             RegularExpression["([a-zA-Z0-9])*"]] && t[[2]] == " ", 
           t = SplitBy[t, " " == # &] // DeleteCases@{" "};
           t = SplitBy[t, {"//"} == # &] // DeleteCases@{{"//"}};

           t = Map[
             Map[If[(Characters[#])[[1, 1]] == "\"", #, 
                 StringJoin[{"\"", #, "\""}]] &, #] &, t];

           t = Map[
             StringJoin[StringTrim[#[[1]], "\""], "[", 
               StringJoin@Riffle[#[[2 ;;]], ","], "]"] &, t];
           (*Print["beforeRiffleStringJoin",t];*)

           t[[1]] = t[[1]] <> "[]";

           t = StringJoin@Riffle[t, " // "] <> "" <> 
             addbacksemicolon;
           (* Print@t;*)
           t, t]] &,
        t = (SplitBy[If[Depth[t] == 2, t, t[[1]]], 
            "\[IndentingNewLine]" == # &] // 
           DeleteCases@{"\[IndentingNewLine]"});
        t
        ];
      t = ToExpression@StringJoin@Riffle[t, "\[IndentingNewLine]"];
      t
      ]]];
cd[x_] := Function[SetDirectory[x];]
fn = Function[Null, 
   ReplacePart[
    Function @@ 
     Hold[Null, Quiet[## &, {Function::slotn}], HoldAll], {2, 1, 0} ->
      Function @@ Hold[##]], HoldAll];
cmds = {echo, cat, ls, wget, grep, ps, top, df, killv, rm, cp, mv, 
   cat, mount, chmod, chown, passwd, mkdir, ifconfig, uname, whereis, 
   whatis, locate, find, sed, awk, diff , sort, xargs, uname, 
   ifconfig, locate , man, tail, less, ping, date};
Map[Function[{f},
   With[{n = ToString[Unevaluated[f]]},
    f[x___ : String] := fn[
       Import[
        "!LD_LIBRARY_PATH= " <> 
         If[ToString@#1 == "#1", "", "echo \"" <> #1 <> "\" | "] <> 
         n <> " " <> StringJoin@Riffle[{x}, " "], "Text"]
       ];
    ]
   ], cmds];
William
  • 7,595
  • 2
  • 22
  • 70
  • Cool! I've played with it a little bit and it's probably better to replace the last Print with CellPrint[ ExpressionCell[ Symbol[StringTrim[t[[1]], "\""]] @@ ToString /@ t[[2 ;;]], "Output"]] – swish May 12 '16 at 23:02
  • @swish thank you for the upvote(if it was you)! Your code does appear to be shorter I will mess with it a bit. I am trying to figure out if I can generalize the approach above to work for more then just one line. – William May 13 '16 at 02:01