7

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?

Ray Shadow
  • 7,816
  • 1
  • 16
  • 44

1 Answers1

4

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

jkuczm
  • 15,078
  • 2
  • 53
  • 84