6
$\begingroup$

When Mathematica encounters a syntax error like a./b it generates a red bracket and highlights offending symbols with yellow background. I would like to add some custom checks which work in the same way.

For example I would like to highlight double commas in a Module statement in order to prevent localization of Null variable:

Module[{a, ,c}, Null]

(While this is a valid Mathematica expression, I would like to flag it, because in my use cases this can only be a typo.)

I have looked at SyntaxInformation, but it can only check the number of arguments but not the values.

Is there a simple way to add a custom syntax check which highlights offending symbol and bracket?

$\endgroup$
  • $\begingroup$ That's funny ... never realized that Null can be a variable. :^) $\endgroup$ – Alexey Popkov Mar 18 '17 at 11:19
4
$\begingroup$

You can use $PreRead to intercept boxes, before they are parsed to an expression, manipulate them as you like, and overwrite EvaluationCell using NotebookWrite.

Following syntaxErrorsPreRead function automates this process.

$syntaxErrorsPreReadUse = True;
syntaxErrorsPreRead // ClearAll
syntaxErrorsPreRead[parser_] := Module[{errors = False, cell, boxes, newBoxes},
    If[Not@$syntaxErrorsPreReadUse, Return[#, Module]];
    cell = EvaluationCell[];
    If[cell === $Failed, Return["", Module]];
    cell = NotebookRead@cell;
    boxes = First@cell;
    {newBoxes, errors} = parser@boxes;
    If[boxes === newBoxes,
        If[errors, "", #]
    (* else *),
        cell[[1]] = newBoxes;
        If[errors,
            NotebookWrite[EvaluationCell[],
                Append[cell, EmphasizeSyntaxErrors -> True]
            ]
        (* else *),
            NotebookWrite[EvaluationCell[], cell, All];
            Block[{$syntaxErrorsPreReadUse = False},
                SelectionEvaluateCreateCell@EvaluationNotebook[]
            ]
        ];
        ""
    ]
]&;

It accepts one argument: a parser. parser should be a function that accepts arbitrary boxes, and returns pair: {parsedBoxes, errors} where parsedBoxes are boxes with marked syntax errors, and errors is a Boolean: True if errors were found in boxes, and False if not.

syntaxErrorsPreRead[parser] returns a function that can be assigned to $PreRead.

Example of parser, that marks, as errors, adjacent commas, is given below.

$errorStyle = CurrentValue@{AutoStyleOptions, "EmphasizedSyntaxErrorStyle"};

adjacentCommaPosition = Module[{prevComma = {{}}}, {
    MapIndexed[
        Replace[#1, {
            "," :> Replace[prevComma, {
                {} :> (prevComma = First@#2; {}),
                _ :> {prevComma, prevComma = First@#2}
            }],
            "Null" :> (prevComma = {}; {}),
            "(" | "[" | "{" :> (prevComma = {{}}; {}),
            _ :> (
                If[Quiet@MakeExpression@#1 =!= HoldComplete@Null,
                    prevComma = {}
                ];
                {}
            )
        }]&,
        #
    ],
    prevComma
} // Flatten // List // Transpose]&;

markAdjacentCommasSyntaxErrors = Module[{errors = False}, {
    # /. StyleBox[x_, "customSyntaxError", ___] :> x /.
        RowBox@l_List :> With[{pos = adjacentCommaPosition@l},(
            errors = True;
            RowBox@MapAt[StyleBox[#, "customSyntaxError", $errorStyle]&, l, pos]
        ) /; pos =!= {}],
    errors
}]&;

After setting $PreRead:

$PreRead = syntaxErrorsPreRead[markAdjacentCommasSyntaxErrors];

and evaluating cells with errors we get:

custom syntax errors

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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