Files
svc-api-x/v1/lib/expression_parser.cfc

187 lines
11 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<cfcomponent>
<!--- разбирает и вычисляет выражения из вложенных функций для зависимых CFS параметров --->
<cfset this.args = []/>
<cfproperty name="entity" type="string"/>
<cffunction name="init">
<cfargument name="entity" type="string" default="instance_operation_cfs_param"/>
<cfset this.entity=arguments.entity/>
<cfreturn this>
</cffunction>
<cffunction name="eval" returntype="any" access="public">
<cfargument name="expression" type="string"/>
<cfargument name="context" type="any"/><!--- {component,key,params} --->
<!--- Если параметр сохранен, то вся нужная информация может быть получена по его ключу. Просто и изящно, но.
Но если параметр, операция, инстанс не сохранены в базе - то нам придется предоставить с контекстом все, что может понадобиться
При этом контекст кажется вывернутым наизнанку: не инстанс / содержащий операции /содержащие параметры, а параметр, что-то знающий про операцию и инстанс
А бывают нужны
- параметры-зависимости (мы их специально перечисляем в отдельном поле)
- псевдопараметр resourceRealm, может наследоваться от предка (предок определен как раз параметром, инстансу существовать не обязательно) (нужно ли декларировать параметр-ссылку на предка в зависимостях?)
- стейт (есть только у созданного инстанса и вычисляется по ключу instance_uid)
Еще имя компонента
Например, контекст может содержать инстанс компонента, имя компонента и дополнительные параметры
По имени компонента мы можем сразу создать пустой (неинициализированный инстанс)
Ну и как ему передать параметры? У инстанса есть массив параметров. Точнее, map<any> (удобнее адресоваться именами). Так и передать. А дальше ресолвить по мере сил (это работа самого компонента)
Контекст сам выглядит компонентом, но поскольку его использование кажется локальным, мы можем взять произвольную структуру
Если компонент-параметр инстанциирован, мы можем получить из БД значения всех соседних параметров.
Но даже если компонент инстанциирован, непосредственно переданные значения параметров должны иметь приоритет перед сохраненными (мы сначала редактируем зависимые параметры, и только потом одним чохом записываем коллекцию параметров в БД
Логично использовать ленивую инициализацию
В этом рассуждении я упустил такой момент. Конкретно для параметра (хотя я хотел абстрагироваться от сущности) есть родительские сущности Операция и Инстанс,
и какая-то из них может быть задана. Это не так же точно, как собственный ключ, но несет некоторую полезную информацию. Может быть, передавать массив или map ключей?
--->
<cfset var args=[]/>
<cfset parse(args, tokenize(arguments.expression),0)/>
<cfreturn computeExpression(args[1].function, args[1].args, arguments.context)/>
</cffunction>
<cffunction name="extractParams" returntype="any" access="public">
<cfargument name="expression" type="string"/>
<cfset var params=[]/>
<cfset parseForParams(params, tokenize(arguments.expression),0)/>
<cfreturn params/>
</cffunction>
<!---
expression parse-evaluate
для навигации по стейтам и конфигурациям платформ
*** размещаем здесь временно. скорее всего, парсер надо оформить компонентом
Отдельный вопрос, как адресоваться к аргументам. Или парсер может возвращать функцию, которая будет работать в адресном пространстве параметра? Тогда ему никакой доступ не нужен.
Было бы интересно сериализовывать скомпилированное выражение
Но сейчас все вычисления происходят в самом парсере
*** сначала мы хотим отработать генерацию зависимых списков в deck-tool
*** нужно обрабатывать исключения разбора
--->
<cffunction name="tokenize" returntype="array" access="private">
<cfargument name="expression" type="string"/>
<cfset var local={}/>
<cfset var pattern='"(.*?)"|\w+(\s*\.\s*\w+)*|\(|\)'/>
<cfset var res=rematch(pattern,expression)/>
<cfset var tokens = []/>
<cfloop array=#res# item="local.t">
<cfset tmp=trim(local.t)/>
<cfif len(tmp)>
<cfset arrayAppend(tokens,tmp)/>
</cfif>
</cfloop>
<cfreturn tokens/>
</cffunction>
<cffunction name="tokenType" output=false access="private">
<cfargument name="token"/>
<cfif token EQ "(">
<cfreturn "("/>
<cfelseif token EQ ")">
<cfreturn ")"/>
<cfelseif left(token,1) EQ '"'><!--- не вдаваясь в лишние подробности... внятнее было парсить строку посимвольно --->
<cfreturn "string"/>
<cfelseif find(".",token)>
<cfreturn "variable"/>
<cfelse>
<cfreturn "function"/><!--- будем проще: если дальше не найдем аргументов - бросим исключение --->
</cfif>
</cffunction>
<cffunction name="parse" returntype="numeric" access="private"><!--- returns last token position --->
<cfargument name="args" type="array"/><!--- puts here parsed tree with nested functions --->
<cfargument name="tokens" type="array"/>
<cfargument name="positionBefore" type="numeric"/><!--- before first processed token, initially 0 --->
<cfset var i = arguments.positionBefore/>
<cfloop condition="++i LE arrayLen(arguments.tokens)">
<cfswitch expression=#tokenType(arguments.tokens[i])#>
<cfcase value="string">
<cfset arrayAppend(arguments.args,mid(arguments.tokens[i],2,len(arguments.tokens[i])-2))/>
</cfcase>
<cfcase value="variable">
<cfset arrayAppend( arguments.args, {"function"="getValue","args"=[tokens[i]]} )/>
</cfcase>
<cfcase value="function">
<cfset var f = {"function"=arguments.tokens[i],"args"=[]}/>
<cfif i GE arrayLen(arguments.tokens)>
<cfthrow message="unexpected end of expression"/>
</cfif>
<cfif #tokenType(arguments.tokens[++i])# NEQ "("><!--- skip opening parenthesis ---><!--- *** possible out of range --->
<cfthrow message="function syntax incorrect" detail="function #arguments.tokens[i-1]#, token='#arguments.tokens[i]#' (#arrayToList(tokens,' ')#"/>
</cfif>
<cfset i = parse(f.args, arguments.tokens, i)/>
<cfset arrayAppend(arguments.args,f)/>
</cfcase>
<cfcase value=")">
<cfreturn i/><!--- returns last token position --->
</cfcase>
</cfswitch>
</cfloop>
</cffunction>
<cffunction name="parseForParams" returntype="numeric" access="private"><!--- returns last token position --->
<cfargument name="args" type="array"/><!--- puts here parsed params --->
<cfargument name="tokens" type="array"/>
<cfargument name="positionBefore" type="numeric"/><!--- before first processed token, initially 0 --->
<cfset var i = arguments.positionBefore/>
<cfloop condition="++i LE arrayLen(arguments.tokens)">
<cfswitch expression=#tokenType(arguments.tokens[i])#>
<cfcase value="string">
<!--- skip ---><!--- <cfset arrayAppend(arguments.args,mid(arguments.tokens[i],2,len(arguments.tokens[i])-2))/>--->
</cfcase>
<cfcase value="variable">
<cfset arrayAppend( arguments.args, tokens[i] )/>
</cfcase>
<cfcase value="function">
<cfset var f = {"function"=arguments.tokens[i],"args"=[]}/>
<cfif i GE arrayLen(arguments.tokens)>
<cfthrow message="unexpected end of expression"/>
</cfif>
<cfif #tokenType(arguments.tokens[++i])# NEQ "("><!--- skip opening parenthesis ---><!--- *** possible out of range --->
<cfthrow message="function syntax incorrect" detail="function #arguments.tokens[i-1]#, token='#arguments.tokens[i]#' (#arrayToList(tokens,' ')#"/>
</cfif>
<cfset i = parseForParams(f.args, arguments.tokens, i)/>
<!--- <cfset arrayAppend(arguments.args,f)/> --->
</cfcase>
<cfcase value=")">
<cfreturn i/><!--- returns last token position --->
</cfcase>
</cfswitch>
</cfloop>
</cffunction>
<cffunction name="computeExpression" returntype="any" access="private"><!--- вычисление выражения снаружи внутрь --->
<cfargument name="function" type="string"/>
<cfargument name="args" type="array"/>
<cfargument name="context" type="any"/>
<cfloop index="i" from=1 to=#arrayLen(arguments.args)#><!--- тонкость: цикл item по массиву копирует элемент, а нам нужно его менять по ссылке --->
<cfif isStruct(arguments.args[i]) AND structKeyExists(arguments.args[i],"function")> <!--- и все равно это ненадежный способ отличать функцию от простого значения --->
<cfset arguments.args[i] = computeExpression(
arguments.args[i].function,
arguments.args[i].args,
arguments.context
) />
</cfif>
</cfloop>
<cfreturn executeFunction(arguments.function,arguments.args,arguments.context)/>
</cffunction>
<cffunction name="executeFunction" returntype="any" access="private"><!--- эта функция обращается к прикладным функциям, должна их видеть --->
<cfargument name="functionName"/><!--- а ссылку на функцию передать не получится? (замыкание даже наверно не нужно) --->
<cfargument name="args" type="array"/>
<cfargument name="context" type="any"/>
<!--- <cfdump var=#arguments#/> --->
<cfset var obj = createObject("component", arguments.context.component).init(arguments.context.keys, arguments.context.params)/>
<!--- <cfdump var=#obj# abort=true/> --->
<cfreturn invoke(obj, functionName, args)/>
</cffunction>
</cfcomponent>