shuttle-formula是一个公式编辑器,支持公式解析、公式计算、原生js渲染、react渲染、vue渲染
该部分是shuttle-formula的核心包,提供词法分析、语法分析、语法检查、计算表达式等功能
import { LexicalAnalysis, useAllTokenParse } from 'shuttle-formula/core'
// 初始化一个词法分析器
const lexicalAnalysis = new LexicalAnalysis()
// 使用内置的词法解析工具
useAllTokenParse(lexicalAnalysis)
// 使用此法分析器分析一段表达式
lexicalAnalysis.setCode('$a.b.c + @sum(10, $a.d) >= 10.8')
const tokens = await lexicalAnalysis.execute()
// 得到分析后的tokens
console.log(tokens)
// 使用更新代码,减少计算时间,如下更新后代码为:$a.test.c + @sum(10, $a.d) >= 10.8
const updateTokens = await lexicalAnalysis.spliceCode(3, 1, 'test')
// 得到更新后的tokens
console.log(updateTokens)
import { SyntaxAnalysis } from 'shuttle-formula/core'
// 初始化一个语法分析器
const syntaxAnalysis = new SyntaxAnalysis()
// 将词法分析的结果作为输入,进行语法分析
syntaxAnalysis.setTokenDesc(tokens)
const { syntaxRootIds, syntaxMap } = await syntaxAnalysis.execute()
console.log(syntaxRootIds) // 语法分析后得到的根结点的id列表(可能有多个)
console.log(syntaxMap) // 所有语法结果映射表
import { SyntaxCheck, useAllChecker } from 'shuttle-formula/core'
// 初始化一个语法检查器
const syntaxCheck = new SyntaxCheck()
// 使用内置的语法检查规则
useAllChecker(syntaxCheck)
// 设置语法检查时通过变量路径获取变量定义的函数
type GetVariableDefine = (
path: string[],
) => WithPromise<WithUndefined<VariableDefine.Desc>>
syntaxCheck.setGetVariableFu(fn: GetVariableDefine)
// 设置语法检查时通过函数名称获取函数定义的函数
type GetFunctionDefine = (
name: string,
) => WithPromise<WithUndefined<FunctionDefine.Desc>>
syntaxCheck.setGetFunctionFu(fn: GetFunctionDefine)
const checkRes = await syntaxCheck.check(syntaxRootIds, syntaxMap)
// 若检查结果有语法错误则返回一个错误对象
// 若检查结果没有语法错误,则返回所有语法块对应的返回值类型
console.log(checkRes)
import { CalculateExpression, useAllComputer } from 'shuttle-formula/core'
// 初始化一个表达式计算器
const calculateExpression = new CalculateExpression()
// 使用内部的计算器
useAllComputer(calculateExpression)
// 设置计算器通过变量路径获取变量值的函数
type GetVariable = (path: string[]) => WithPromise<WithUndefined<any>>
calculateExpression.setGetVariableFu(fn: GetVariable)
// 设置计算器通过函数名获取函数值的函数
type GetFunction = (name: string) => WithPromise<WithUndefined<Function>>
calculateExpression.setGetFunctionFu(fn: GetFunction)
// 通过检查的语法,则可直接放入计算器中进行计算
const value = await calculateExpression.execute(syntaxRootIds, syntaxMap)
console.log(value)
该部分提供shuttle-formula的基础web渲染能力,提供灵活的插件入口,可在此基础上扩展能力,定制化公式编辑器
import { Render } from 'shuttle-formula/render'
const render = new Render({
useWorker: true, // 设置是否使用web worker进行语法分析,当表达式很长时可使得编辑不会卡顿
})
render.setDomStyle(
'border-radius: 5px; box-shadow: 0 0 6px 0 #ccc; width: 400px',
)
const root = document.createElement('div')
root.setAttribute('style', 'display: flex; justify-content: center;')
render.mount(root)
document.body.append(root)
// 设置自定义变量的描述,使得编辑器知道有哪些变量,以及这些变量的类型,用于语法检查以及提示;由于此处不涉及到计算,所以不需要变量的值
import {
WithDynamicVariable,
GetDynamicObjectByPath,
} from 'shuttle-formula/render'
const variables: Record<string, WithDynamicVariable> = {
a: {
type: 'object',
label: '变量的label',
prototype: {
c: {
type: 'number',
},
e: {
type: 'object',
dynamic: true, // object变量可设置dynamic,表示该变量的属性通过远程获取,只有当用到该变量时才会去获取
},
},
},
}
const getDynamicObjectByPath: GetDynamicObjectByPath = (path, define) => {
return new Promise((resolve) => {
setTimeout(() => {
if (path[0] === 'a' && path[1] === 'e') {
resolve({
type: 'object',
prototype: {
test: {
label: '测试异步',
type: 'number',
},
},
})
} else {
resolve(undefined)
}
}, 500)
})
}
// 将定义的变量添加到渲染器中
render.setVariables(variables)
// 将获取异步变量的方法添加到渲染器中,若不存在异步变量可不设置
render.setGetDynamicObjectByPath(getDynamicObjectByPath)
// 设置自定义函数的描述,使得编辑器知道有哪些函数,以及这些函数的入参以及返回值类型,用于语法检查以及提示;由于此处不涉及到计算,所以不需要函数的值
import { VariableDefine, SyntaxError } from 'shuttle-formula/core'
import { WithLabelFunction, FunctionGroup } from 'shuttle-formula/render'
function createError(
type: SyntaxError.Desc['type'],
syntaxId: string,
msg: string,
): SyntaxError.Desc {
return { type, syntaxId, msg }
}
const functionWithGroups: FunctionGroup[] = [
{
id: 'create',
label: '创建相关',
functions: {
createObject: {
label: '创建对象',
params: [{ define: { type: 'string' } }, { forwardInput: true }],
loopAfterParams: 2,
return: {
scope: 'customReturn',
createType: async (getType, ...params) => {
const defReturn: VariableDefine.Object = {
type: 'object',
prototype: {},
}
for (let i = 0; i < params.length; i += 2) {
const currentParams = params[i]
if (i + 1 >= params.length) {
return {
pass: false,
error: createError(
'functionError',
currentParams.id,
'键未匹配值',
),
}
}
if (
!SyntaxDescUtils.IsConst(currentParams) ||
currentParams.constType !== 'string'
) {
return {
pass: false,
error: createError(
'functionError',
currentParams.id,
'键名字能是常量字符串',
),
}
}
const valueParams = params[i + 1]
const valueParamsType = await getType(valueParams.id)
if (!valueParamsType) {
return {
pass: false,
error: createError(
'functionError',
valueParams.id,
'未找到参数类型',
),
}
}
const key = currentParams.valueTokens
.map((token) => token.code)
.join('')
defReturn.prototype[key] = { ...valueParamsType }
}
return {
pass: true,
type: defReturn,
}
},
},
},
createArray: {
label: '创建数组',
params: [{ forwardInput: true }],
loopAfterParams: 1,
loopParamsMustSameWithFirstWhenForwardInput: true,
return: {
scope: 'forwardParamsArray',
item: { scope: 'forwardParams', paramsIndex: 0 },
},
},
random: {
label: '随机数',
params: [],
return: { type: 'number' },
},
},
},
{
id: 'transform',
label: '转换',
functions: {
anyToString: {
label: '转字符串',
params: [{ forwardInput: true }],
return: { type: 'string' },
},
},
},
{
id: 'computed',
label: '计算',
functions: {
len: {
label: '计算长度',
params: [{ define: [{ type: 'string' }, { type: 'array' }] }],
return: { type: 'number' },
},
round: {
label: '四舍五入',
params: [{ define: { type: 'number' } }],
return: { type: 'number' },
},
},
},
]
// 将定义的函数组添加到render中
render.setFunctions(functionWithGroups)
// 或者直接设置函数,不使用分组
// const functions: Record<string, WithLabelFunction> = {}
// render.setFunctions(functions)
import { TokenBaseRender } from 'shuttle-formula/render'
class CustomTokenRender extends TokenBaseRender<TokenDesc> {
static TokenType = 'token-type'
}
render.useTokenRender(CustomTokenRender)
import { ErrorDisplay } from 'shuttle-formula/render'
class ErrorDisplayClass implements ErrorDisplay {
// 自定义逻辑
}
render.errorRender.setDisplayFactory(ErrorDisplayClass)
const functionWrapper = document.createElement('div')
render.tipRender.setFunctionPicker({
updateTipOption(tipOption) {},
setOnSelect(onSelect) {},
getRoot() {
return functionWrapper
},
})
const variableWrapper = document.createElement('div')
render.tipRender.setVariablePicker({
updateTipOption(tipOption) {},
setOnSelect(onSelect) {},
getRoot() {
return variableWrapper
},
})
变量都支持label属性,用于提示
变量类型 | 其他属性 | 说明 |
---|---|---|
number | 无 | 数字 |
string | 无 | 字符串 |
boolean | 无 | 布尔 |
array | item: 变量 | 数组 |
object | prototype: Record<string, 变量> | 对象 |
object | dynamic: true | 异步对象 |
函数描述说明
属性 | 类型 | 说明 |
---|---|---|
params | Params[] | 函数参数定义 |
loopAfterParams | number | 表示重复最后几个参数的类型 |
loopParamsMustSameWithFirstWhenForwardInput | boolean | 当入参数定义为forwardInput时,同时又定义了loopAfterParams,表示后面的输入是否需要与第一个输入的数据类型相同 |
return | Return | 函数返回值说明 |
Params
// 基础params: 定义当前参数只能是指定类型或指定的多个类型
{
define: { type: 变量类型 } | Array<{ type: 变量类型 }>
}
// 任意类型params: 根据用户实际输入的类型推断出参数的类型
{
forwardInput: true
}
Return
// ConfirmReturn: 直接定义好的数据类型,与变量类型相同(不支持动态类型)
// ForwardParamsReturn: 表示与第几个入参的类型相同
{
scope: 'forwardParams'
paramsIndex: number
}
// ForwardParamsArray: 以item为数据项类型的数组
{
scope: 'forwardParamsArray'
item: Return
}
// CustomReturn: 自定义返回值类型,具体声明可查看ts文件
{
scope: 'customReturn'
createType: () =>
WithPromise<
{ pass: false; error: SyntaxError.Desc } | { pass: true; type: 变量类型 }
>
}
提供对接react的渲染方式
import {
Render,
Provider,
VariableTip,
FunctionTip,
TokenRender,
ErrorRender,
TokenRenderProps,
ErrorRenderComponentProps,
// useRender: 能获得shuttle-formula/render中的Render对象,可扩展自定义功能
} from 'shuttle-formula/render-react'
import { BooleanTokenDesc, BooleanTokenParse } from 'shuttle-formula/core'
function Example() {
return (
<Provider>
<Render
style={{ borderRadius: 5, boxShadow: '0 0 6px 0 #ccc', width: 400 }}
/>
<VariableTip />
<FunctionTip />
<ErrorRender RenderComponent={TestErrorRender} />
<TokenRender
useTokenType={BooleanTokenParse.Type}
RenderComponent={TestTokenTender}
/>
</Provider>
)
}
function TestTokenTender({ token, type }: TokenRenderProps<BooleanTokenDesc>) {
return <span style={{ color: 'red' }}>{token.code}</span>
}
function TestErrorRender({ error }: ErrorRenderComponentProps) {
return <div style={{ color: 'blue' }}>自定义错误提示: {error?.msg}</div>
}
提供对接vue的渲染方式
<script setup lang="ts">
import {
Render,
Provider,
// VariableTip: 自定义变量提示
// FunctionTip: 自定义函数提示
// TokenRender: 自定义token渲染
// ErrorRender: 自定义error渲染
// useRender: 能获得shuttle-formula/render中的Render对象,可扩展自定义功能
} from 'shuttle-formula/render-vue'
import { BooleanTokenDesc, BooleanTokenParse } from 'shuttle-formula/core'
</script>
<template>
<Provider>
<Render
style="border-radius: 5px; box-shadow: 0 0 6px 0 #ccc; width: 400px"
/>
</Provider>
</template>