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:

Null
can be a variable. :^) $\endgroup$