This repository has been archived by the owner on Aug 25, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6f2c274
commit b58921b
Showing
7 changed files
with
679 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
export const grammar = `/* description: Parses and executes mathematical expressions. */ | ||
/* lexical grammar */ | ||
%lex | ||
%% | ||
\s+ /* skip whitespace */ | ||
[0-9]+("."[0-9]+)?\b return 'NUMBER'; | ||
"*" return '*'; | ||
"/" return '/'; | ||
"-" return '-'; | ||
"+" return '+'; | ||
"^" return '^'; | ||
"!" return '!'; | ||
"%" return '%'; | ||
"(" return '('; | ||
")" return ')'; | ||
"PI" return 'PI'; | ||
"E" return 'E'; | ||
<<EOF>> return 'EOF'; | ||
. return 'INVALID'; | ||
/lex | ||
/* operator associations and precedence */ | ||
%left '+' '-' | ||
%left '*' '/' | ||
%left '^' | ||
%right '!' | ||
%right '%' | ||
%left UMINUS | ||
%token INVALID | ||
%start expressions | ||
%% /* language grammar */ | ||
expressions | ||
: e EOF | ||
{ typeof console !== 'undefined' ? console.log($1) : print($1); | ||
return $1; } | ||
; | ||
e | ||
: e '+' e | ||
{$$ = $1 + $3;} | ||
| e '-' e | ||
{$$ = $1 - $3;} | ||
| e '*' e | ||
{$$ = $1 * $3;} | ||
| e '/' e | ||
{$$ = $1 / $3;} | ||
| e '^' e | ||
{$$ = Math.pow($1, $3);} | ||
| e '!' | ||
{{ | ||
$$ = (function fact(n) { | ||
return n == 0 ? 1 : fact(n - 1) * n; | ||
})($1); | ||
}} | ||
| e '%' | ||
{$$ = $1 / 100;} | ||
| '-' e %prec UMINUS | ||
{$$ = -$2;} | ||
| '(' e ')' | ||
{$$ = $2;} | ||
| NUMBER | ||
{$$ = Number(yytext);} | ||
| E | ||
{$$ = Math.E;} | ||
| PI | ||
{$$ = Math.PI;} | ||
; | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import React, { useState, useEffect, useReducer } from 'react'; | ||
import { parser } from './parser' | ||
import { TypeAheadSelect } from 'patternfly-react'; | ||
import Autosuggest from 'react-autosuggest'; | ||
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; | ||
const nearley = require("nearley"); | ||
const partialGrammar = require('./grammar.ne.js') | ||
let partialParser = new nearley.Parser(nearley.Grammar.fromCompiled(partialGrammar)); | ||
|
||
const autocomplete = { | ||
exp_type: ["FIELD:", "TAGS:", "COUNT OF:", "FIND:", "REGKEY:"], | ||
entity: ["vm.","host."], | ||
field: ['name ', 'status '], | ||
category: ['environment ', 'category '], | ||
operator: ['= ', 'CONTAINS '], | ||
tag_operator: ['= ', 'CONTAINS ', ': '], | ||
exp_operator: ['AND ', 'OR '], | ||
value: ['value '], | ||
check: ['CHECK ALL:', 'CHECK ANY:', 'CHECK COUNT:'] | ||
} | ||
|
||
const getSuggestions = next => autocomplete[next] || next.map(n => autocomplete[n]).flat() | ||
|
||
const initialState = { | ||
inputText: "", | ||
parsedAST: "", | ||
partialAST: {}, | ||
isValid: true, | ||
caretPosition: 0, | ||
suggestions: [], | ||
filteredSuggestions: [], | ||
filterStr: "", | ||
} | ||
|
||
const reducer = (state, action) => { | ||
console.log(action); | ||
const { type, inputText, caretPosition, keyCode, ctrlKey } = action; | ||
switch (type) { | ||
case 'onKeyDown': | ||
const { parsedAST, isValid } = parse(inputText); | ||
let newState = {...state}; | ||
let next = []; | ||
try { | ||
partialParser = new nearley.Parser(nearley.Grammar.fromCompiled(partialGrammar)); | ||
let currentExp = inputText.slice(0,caretPosition)+"|"; | ||
const leftIndex = Math.max(currentExp.lastIndexOf('AND'), currentExp.lastIndexOf('OR'), 0); | ||
console.log(currentExp.slice(leftIndex).replace(/AND|OR/,'')); | ||
partialParser.feed(currentExp.slice(leftIndex).replace(/AND|OR/,'')); | ||
next = partialParser.results[0][0].next; | ||
console.log(next); | ||
newState.partialAST = partialParser.results[0][0].results; | ||
} | ||
catch(err) { | ||
console.log(err.message); | ||
} | ||
const filterStr = newState.partialAST.slice(-1)[0] ? newState.partialAST.slice(-1)[0].value : ''; | ||
console.log(filterStr); | ||
const suggestions = getSuggestions(next); | ||
const filteredSuggestions = suggestions.filter(x => x.toLowerCase().includes(filterStr.toLowerCase())); | ||
return {...newState, | ||
inputText, | ||
parsedAST, | ||
isValid, | ||
caretPosition, | ||
suggestions, | ||
filteredSuggestions, | ||
filterStr, | ||
} | ||
default: | ||
return state; | ||
|
||
} | ||
} | ||
|
||
const parse = (text, callback) => { | ||
try { | ||
return { parsedAST: JSON.stringify(parser.parse(text)), isValid: true}; | ||
} | ||
catch(err) { | ||
return { parsedAST: err.message, isValid: false}; | ||
} | ||
} | ||
|
||
const statusDiv = (isValid) => (( | ||
isValid | ||
? <div style={{backgroundColor: 'lightGreen'}}>{isValid ? 'Expression je validni' : 'Expression JE validni'}</div> | ||
: <div style={{backgroundColor: 'red'}}>{isValid ? 'Expression je validni' : 'Expression NENI validni'}</div> | ||
)); | ||
|
||
const generateMenu = (item) => <li style={{width: '500px', 'font-size':'14px'}}>{item}</li> | ||
|
||
export default function ExpressionEditor() { | ||
const [state, dispatch] = useReducer(reducer, initialState); | ||
|
||
const onChange = (e, { newValue, method }) => { | ||
switch (method) { | ||
case 'type': | ||
dispatch({ | ||
type: 'onKeyDown', | ||
caretPosition: e.target.selectionStart, | ||
inputText: e.target.value, | ||
keyCode: e.keyCode, | ||
ctrlKey: e.ctrlKey | ||
}) | ||
break; | ||
case 'click': | ||
console.log('CLICK'); | ||
const leftPart = state.inputText.slice(0, state.caretPosition); | ||
const rightPart = state.inputText.slice(state.caretPosition); | ||
const cutedLeftPart = leftPart.slice(0, leftPart.length - state.filterStr.length); | ||
console.log(`${cutedLeftPart}${newValue}${rightPart}`, e.target.sel); | ||
dispatch({ | ||
type: 'onKeyDown', | ||
caretPosition: state.caretPosition + newValue.length - state.filterStr.length, | ||
inputText: `${cutedLeftPart}${newValue}${rightPart}`, | ||
ctrlKey: false | ||
}) | ||
break; | ||
default: | ||
|
||
} | ||
} | ||
|
||
// const a = (<TypeAheadSelect | ||
// id="ee" | ||
// options={state.suggestions} | ||
// emptyLabel={null} | ||
// filterBy={(x)=>(x)} | ||
// isValid={state.isValid} | ||
// isInvalid={!state.isValid} | ||
// onChange={e=>console.log(e)} | ||
// inputProps={{onKeyUp: e => dispatch({type: 'onKeyDown', caretPosition: e.target.selectionStart, inputText: e.target.value, keyCode: e.keyCode, ctrlKey: e.ctrlKey })}} | ||
// filterBy={(option, props) => { | ||
// return option.includes(state.filterStr) | ||
// }} | ||
// > | ||
// | ||
// </TypeAheadSelect>); | ||
// const b = (<input | ||
// // onChange={e => setInputText(e.target.value)} | ||
// onKeyDown={e => dispatch({type: 'onKeyDown', caretPosition: e.target.selectionStart, inputText: e.target.value, keyCode: e.keyCode, ctrlKey: e.ctrlKey })} | ||
// />); | ||
console.log(state.suggestions, state.filteredSuggestions, state.filterStr); | ||
const c = (<Autosuggest | ||
|
||
suggestions={state.filteredSuggestions} | ||
getSuggestionValue={x=>x} | ||
inputProps={{value: state.inputText, onChange: (e, action) => onChange(e, action), | ||
onKeyUp: (e, action) => dispatch({ | ||
type: 'onKeyDown', | ||
caretPosition: e.target.selectionStart, | ||
inputText: e.target.value, | ||
}), | ||
onClick: e => console.log(e.target.selectionStart), style: {width: '500px', 'font-size':'14px'}}} | ||
renderSuggestion={generateMenu} | ||
onSuggestionsFetchRequested={x => x} | ||
onSuggestionSelected={ (e, { method, suggestionValue }) => onChange(e,{method, newValue: suggestionValue})} | ||
alwaysRenderSuggestions | ||
/>) | ||
|
||
return ( | ||
<div> | ||
{c} | ||
|
||
{statusDiv(state.isValid)} | ||
<div>{state.parsedAST}</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.