Building an Expression Validator Using React
In this article we are going to design a simple expression validator that can validate user entered expression using some set of rules.
Sometimes writing an expression can be difficult. Generally an expression consists of components like operators, identifiers, constants and functions. So, we are going to create an expression validator for validating the expressions. It can use data like a set of variables or functions coming from an API to make our work easier as it help us to efficiently create an expression by providing the features like hinting the available variable name, highlighting the error in case of an invalid expression, etc.
Implementation
Basically there are two components of expression validator:-
- Mentions dropdown
- Parser
Mentions Dropdown :-
In the expression builder, we show the list of available variables using react-mentions . In the current implementation the list of available options will get triggered whenever the user is entering the '$' character. We can change this using the custom regular expression(regex) as shown:
Here is code snippet of the react-mentions part :-
<MentionsInput
className="customMentionInput"
id="logicTextArea"
value={value}
onChange={(
event: { target: { value: string } },
newValue: string,
newPlainTextValue: string
) => {
hanldeAreaInputValue(newPlainTextValue);
}}
style={defaultMentionStyles}
>
<Mention
trigger={/(?:^|)(\$([^\s$]*))$/} // with space = /(?:^|\s)(\$([^\s\$]*))$/
markup={`$[____display____]`}
data={dataSetForLogic}
displayTransform={(id, display) => `$'${display}'`}
appendSpaceOnAdd={true}
/>
</MentionsInput>
MentionsInput
is like a normal textInput
component which will only accept Mention as a child component. Therefore, we have provided a regex for the trigger property /(?:^|)(\$([^\s$]*))$/
. It will cause the Mention component to trigger querying the data source whenever it encounter's the '$' character.
displayTransform property accepts a function for customising the string that is displayed for a mention.
Parser :-
Parser takes expression as an input, tokenises the data and classifies the tokens into different categories. Here we are classifying the tokens into four different categories:
- Variables
- Identifiers
- Operators
- Numbers
Here is the code snippet to tokenise the data:-
const tokenList = [];
const numberRegex = /[0-9]+(\.[0-9]*)?([eE][+-]?[0-9]+)?/;
const operatorRegex = /AND|OR|and|or|XOR|&&|<=|>=|<|>|!=|==|&|OR*|!|\|{1,2}/;
const variableRegex = /\$'[A-Za-z_][A-Za-z_0-9]+'+((\[([0-9]+)\])*)?/;
const identifierRegex = /[A-Za-z_][A-Za-z_0-9]*/;
const otherCharRegex = /\S/g;
const reToken = new RegExp(
numberRegex.source +
"|" +
operatorRegex.source +
"|" +
variableRegex.source +
"|" +
identifierRegex.source +
"|" +
otherCharRegex.source,
otherCharRegex.flags
);
for (;;) {
const match = reToken.exec(text);
if (match === null) {
break;
}
tokenList.push(new Token(match[0], match.index));
}
Here, we are creating the regex reToken
which will execute the text and return an array containing the result of that search. If the value of match
is not null, a token is inserted into the tokenList
. Classification of token is done using the following logic :-
export class Token {
private text: string;
private index: number;
private kind: "identifier" | "number" | "operator" | "variable";
constructor(text: string, index: number) {
this.text = text;
this.index = index;
// Classify the token.
if (/^\$'[A-Za-z_][A-Za-z_0-9]+'/.test(text)) {
this.kind = "variable";
} else if (/^[A-Za-z_]/.test(text)) {
this.kind = "identifier";
} else if (/^[0-9]/.test(text)) {
this.kind = "number";
} else {
this.kind = "operator";
}
}
}
After classifying the tokens, we will parse the token list using the recursive descent parsing technique. For parser implementation, I have taken the references from the following article .
There are some set of rules that need to be satisfied for generating the parse tree :-
- There must be one operator between the two operands.
- The small parenthesis must be balanced.
- Only the available variable names can be used to create the expression.
If the entered expression satisfies the above rules a parse tree will be generated successfully. An example of this is shown below:
Here is the json
for parse tree corresponding to the input text $'item1'+ $'item2'
:
To Conclude
If the entered expression does not satisfy the required set of rules, then the parser will generate an error message along with the token data wherever the error is found. Here is the sample example of the error message caused due to the wrong variable name and unbalanced parenthesis:
You can play around with the expression editor by visiting the following link:- https://aashishgtbit.github.io/expression-editor/
Github : github.com/Aashishgtbit/expression-editor.
Hope you all liked the article.
Thank you