shuttle-formula

0.0.1 • Public • Published

shuttle-formula

shuttle-formula是一个公式编辑器,支持公式解析、公式计算、原生js渲染、react渲染、vue渲染

shuttle-formula/core

说明

该部分是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/render

说明

该部分提供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)
自定义token渲染
import { TokenBaseRender } from 'shuttle-formula/render'

class CustomTokenRender extends TokenBaseRender<TokenDesc> {
  static TokenType = 'token-type'
}

render.useTokenRender(CustomTokenRender)
自定义error渲染
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: 变量类型 }
    >
}

shuttle-formula/render-react

说明

提供对接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>
}

shuttle-formula/render-vue

说明

提供对接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>

Package Sidebar

Install

npm i shuttle-formula

Weekly Downloads

9

Version

0.0.1

License

MIT

Unpacked Size

434 kB

Total Files

147

Last publish

Collaborators

  • ming_bing