in nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/modes/nfel.ts [144:678]
token: function (stream: StringStream, states: any) {
// consume any whitespace
if (stream.eatSpace()) {
return null;
}
// if we've hit the end of the line
if (stream.eol()) {
return null;
}
// get the current character
const current: string | null = stream.peek();
// if we've hit some comments... will consume the remainder of the line
if (current === '#') {
// consume the pound
stream.next();
const afterPound: string | null = stream.peek();
if (afterPound !== '{') {
stream.skipToEnd();
return 'comment';
} else {
// unconsume the pound
stream.backUp(1);
}
}
// get the current state
const state: any = states.get();
// the current input is invalid
if (state.context === NfEl.INVALID) {
stream.skipToEnd();
return null;
}
// within an expression
if (state.context === NfEl.EXPRESSION) {
const attributeOrSubjectlessFunctionExpression =
/^[^'"#${}()[\],:;\/*\\\s\t\r\n0-9][^'"#${}()[\],:;\/*\\\s\t\r\n]*/;
// attempt to extract a function name
const attributeOrSubjectlessFunctionName: string[] = stream.match(
attributeOrSubjectlessFunctionExpression,
false
);
// if the result returned a match
if (
attributeOrSubjectlessFunctionName !== null &&
attributeOrSubjectlessFunctionName.length === 1
) {
// consume the entire token to better support suggest below
stream.match(attributeOrSubjectlessFunctionExpression);
// if the result returned a match and is followed by a (
if (
self.subjectlessFunctionRegex.test(attributeOrSubjectlessFunctionName[0]) &&
stream.peek() === '('
) {
// --------------------
// subjectless function
// --------------------
// context change to function
state.context = NfEl.ARGUMENTS;
// style for function
return 'builtin';
} else {
// ---------------------
// attribute or function
// ---------------------
// context change to function or subject... not sure yet
state.context = NfEl.SUBJECT_OR_FUNCTION;
// this could be an attribute or a partial function name... style as attribute until we know
return 'variable-2';
}
} else if (current === "'" || current === '"') {
// --------------
// string literal
// --------------
// handle the string literal
const expressionStringResult: string | null = self.handleStringLiteral(stream, state);
// considered a quoted variable
if (expressionStringResult !== null) {
// context change to function
state.context = NfEl.SUBJECT;
}
return expressionStringResult;
} else if (current === '$') {
// -----------------
// nested expression
// -----------------
const expressionDollarResult: string | null = self.handleStart(
'$',
NfEl.EXPRESSION,
stream,
states
);
// if we've found an embedded expression we need to...
if (expressionDollarResult !== null) {
// transition back to subject when this expression completes
state.context = NfEl.SUBJECT;
}
return expressionDollarResult;
} else if (current === '#' && self.parametersSupported) {
// --------------------------
// nested parameter reference
// --------------------------
// handle the nested parameter reference
const parameterReferenceResult = self.handleStart('#', NfEl.PARAMETER, stream, states);
// if we've found an embedded parameter reference we need to...
if (parameterReferenceResult !== null) {
// transition back to subject when this parameter reference completes
state.context = NfEl.SUBJECT;
}
return parameterReferenceResult;
} else if (current === '}') {
// -----------------
// end of expression
// -----------------
// consume the close
stream.next();
// signifies the end of an expression
if (typeof states.pop() === 'undefined') {
return null;
} else {
// style as expression
return 'bracket';
}
} else {
// ----------
// unexpected
// ----------
// consume to move along
stream.skipToEnd();
state.context = NfEl.INVALID;
// unexpected...
return null;
}
}
// within a subject
if (state.context === NfEl.SUBJECT || state.context === NfEl.SUBJECT_OR_FUNCTION) {
// if the next character indicates the start of a function call
if (current === ':') {
// -------------------------
// trigger for function name
// -------------------------
// consume the colon and update the context
stream.next();
state.context = NfEl.FUNCTION;
// consume any addition whitespace
stream.eatSpace();
// don't style
return null;
} else if (current === '}') {
// -----------------
// end of expression
// -----------------
// consume the close
stream.next();
// signifies the end of an expression
if (typeof states.pop() === 'undefined') {
return null;
} else {
// style as expression
return 'bracket';
}
} else {
// ----------
// unexpected
// ----------
// consume to move along
stream.skipToEnd();
state.context = NfEl.INVALID;
// unexpected...
return null;
}
}
// within a function
if (state.context === NfEl.FUNCTION) {
// attempt to extract a function name
const functionName = stream.match(/^[a-zA-Z]+/, false);
// if the result returned a match
if (functionName !== null && functionName.length === 1) {
// consume the entire token to ensure the whole function
// name is matched. this is an issue with functions like
// substring and substringAfter since 'substringA' would
// match the former and when we really want to autocomplete
// against the latter.
stream.match(/^[a-zA-Z]+/);
// see if this matches a known function and is followed by (
if (self.functionRegex.test(functionName[0]) && stream.peek() === '(') {
// --------
// function
// --------
// change context to arguments
state.context = NfEl.ARGUMENTS;
// style for function
return 'builtin';
} else {
// ------------------------------
// maybe function... not sure yet
// ------------------------------
// not sure yet...
return null;
}
} else {
// ----------
// unexpected
// ----------
// consume and move along
stream.skipToEnd();
state.context = NfEl.INVALID;
// unexpected...
return null;
}
}
// within arguments
if (state.context === NfEl.ARGUMENTS) {
if (current === '(') {
// --------------
// argument start
// --------------
// consume the open paranthesis
stream.next();
// change context to handle an argument
state.context = NfEl.ARGUMENT;
// start of arguments
return null;
} else if (current === ')') {
// --------------
// argument close
// --------------
// consume the close paranthesis
stream.next();
// change context to subject for potential chaining
state.context = NfEl.SUBJECT;
// end of arguments
return null;
} else if (current === ',') {
// ------------------
// argument separator
// ------------------
// consume the comma
stream.next();
// change context back to argument
state.context = NfEl.ARGUMENT;
// argument separator
return null;
} else {
// ----------
// unexpected
// ----------
// consume and move along
stream.skipToEnd();
state.context = NfEl.INVALID;
// unexpected...
return null;
}
}
// within a specific argument
if (state.context === NfEl.ARGUMENT) {
if (current === "'" || current === '"') {
// --------------
// string literal
// --------------
// handle the string literal
const argumentStringResult: string | null = self.handleStringLiteral(stream, state);
// successfully processed a string literal...
if (argumentStringResult !== null) {
// change context back to arguments
state.context = NfEl.ARGUMENTS;
}
return argumentStringResult;
} else if (
stream.match(
/^[-\+]?((([0-9]+\.[0-9]*)([eE][+-]?([0-9])+)?)|((\.[0-9]+)([eE][+-]?([0-9])+)?)|(([0-9]+)([eE][+-]?([0-9])+)))/
)
) {
// -------------
// Decimal value
// -------------
// This matches the following ANTLR spec for deciamls
//
// DECIMAL : OP? ('0'..'9')+ '.' ('0'..'9')* EXP? ^([0-9]+\.[0-9]*)([eE][+-]?([0-9])+)?
// | OP? '.' ('0'..'9')+ EXP?
// | OP? ('0'..'9')+ EXP;
//
// fragment OP: ('+'|'-');
// fragment EXP : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
// change context back to arguments
state.context = NfEl.ARGUMENTS;
// style for decimal (use same as number)
return 'number';
} else if (stream.match(/^[-\+]?[0-9]+/)) {
// -------------
// integer value
// -------------
// change context back to arguments
state.context = NfEl.ARGUMENTS;
// style for integers
return 'number';
} else if (stream.match(/^((true)|(false))/)) {
// -------------
// boolean value
// -------------
// change context back to arguments
state.context = NfEl.ARGUMENTS;
// style for boolean (use same as number)
return 'number';
} else if (current === ')') {
// ----------------------------------
// argument close (zero arg function)
// ----------------------------------
// consume the close parenthesis
stream.next();
// change context to subject for potential chaining
state.context = NfEl.SUBJECT;
// end of arguments
return null;
} else if (current === '$') {
// -----------------
// nested expression
// -----------------
// handle the nested expression
const argumentDollarResult: string | null = self.handleStart(
'$',
NfEl.EXPRESSION,
stream,
states
);
// if we've found an embedded expression we need to...
if (argumentDollarResult !== null) {
// transition back to arguments when then expression completes
state.context = NfEl.ARGUMENTS;
}
return argumentDollarResult;
} else if (current === '#' && self.parametersSupported) {
// --------------------------
// nested parameter reference
// --------------------------
// handle the nested parameter reference
const parameterReferenceResult: string | null = self.handleStart(
'#',
NfEl.PARAMETER,
stream,
states
);
// if we've found an embedded parameter reference we need to...
if (parameterReferenceResult !== null) {
// transition back to arguments when this parameter reference completes
state.context = NfEl.ARGUMENTS;
}
return parameterReferenceResult;
} else {
// ----------
// unexpected
// ----------
// consume and move along
stream.skipToEnd();
state.context = NfEl.INVALID;
// unexpected...
return null;
}
}
// within a parameter reference
if (
state.context === NfEl.PARAMETER ||
state.context === NfEl.SINGLE_QUOTE_PARAMETER ||
state.context === NfEl.DOUBLE_QUOTE_PARAMETER
) {
// attempt to extract a parameter name
const parameterName: string[] = stream.match(self.parameterKeyRegex, false);
// if the result returned a match
if (parameterName !== null && parameterName.length === 1) {
// consume the entire token to ensure the whole function
// name is matched. this is an issue with functions like
// substring and substringAfter since 'substringA' would
// match the former and when we really want to autocomplete
// against the latter.
stream.match(self.parameterKeyRegex);
// see if this matches a known function and is followed by (
if (self.parameterRegex.test(parameterName[0])) {
// ------------------
// resolved parameter
// ------------------
// style for function
return 'builtin';
} else {
// --------------------
// unresolved parameter
// --------------------
// style for function
return 'string';
}
}
if (state.context === NfEl.SINGLE_QUOTE_PARAMETER) {
return self.handleParameterEnd(stream, state, states, () => current === "'");
}
if (state.context === NfEl.DOUBLE_QUOTE_PARAMETER) {
return self.handleParameterEnd(stream, state, states, () => current === '"');
}
if (current === '}') {
// -----------------
// end of expression
// -----------------
// consume the close
stream.next();
// signifies the end of an parameter reference
if (typeof states.pop() === 'undefined') {
return null;
} else {
// style as expression
return 'bracket';
}
} else {
// ----------
// unexpected
// ----------
// consume and move along
stream.skipToEnd();
state.context = NfEl.INVALID;
// unexpected...
return null;
}
}
// signifies the potential start of an expression
if (current === '$') {
return self.handleStart('$', NfEl.EXPRESSION, stream, states);
}
// signifies the potential start of a parameter reference
if (current === '#' && self.parametersSupported) {
return self.handleStart('#', NfEl.PARAMETER, stream, states);
}
// signifies the end of an expression
if (current === '}') {
stream.next();
if (typeof states.pop() === 'undefined') {
return null;
} else {
return 'bracket';
}
}
// ----------------------------------------------------------
// extra characters that are around expression[s] end up here
// ----------------------------------------------------------
// consume the character to keep things moving along
stream.next();
return null;
}