SOURCE

function ConditionParser() {
  this.stacks = []
  this.opsMap = {
    '||': 1,
    '&&': 2,
    '>': 3,
    '>=': 3,
    '<': 3,
    '<=': 3,
    '=': 3,
    '!=': 3,
    '+': 4,
    '-': 4,
    '*': 5,
    '/': 5,
    '%': 5
  }
  this.PHReg = /#\{?\s*([a-zA-Z\-\\/.\d]+)\s*\}?(?:\.includes\((.+)\))?/
  this.ops = Object.keys(this.opsMap)
}

ConditionParser.prototype.eval = function (exp, obj) {
  const tokens = this.getToken(exp)
  const rebuildTokens = this.transform(tokens)
  const result = this.compute(rebuildTokens, obj)
  // console.log(`${exp} = ${result}`, rebuildTokens)
  return result
}

ConditionParser.prototype.readPlaceholder = function (exp, tokens) {
  let char = exp[0]
  if (char === '#') {
    if (this.PHReg.test(exp)) {
      const ph = RegExp['$&']
      tokens.push({
        type: 'placeholder',
        value: RegExp.$1,
        includes: RegExp.$2
      })
      exp = exp.substring(ph.length)
    }
  }
  return exp
}

ConditionParser.prototype.readCompareOp = function (exp, tokens) {
  let i = 0
  let char = exp[i]
  const list = [char]
  if (['>', '<'].includes(char)) {
    char = exp[i + 1]
    if (char === '=') {
      list.push(char)
      i += 2
    } else {
      i += 1
    }
    const val = list.join('')
    tokens.push({
      type: 'op',
      value: val,
      priority: this.opsMap[val]
    })
    exp = exp.substring(i)
  } else if (char === '=') {
    i += 1
    tokens.push({
      type: 'op',
      value: char,
      priority: this.opsMap[char]
    })
    exp = exp.substring(i)
  } else if (char === '!' && exp[i + 1] === '=') {
    list.push(exp[i + 1])
    i += 2
    const val = list.join('')
    tokens.push({
      type: 'op',
      value: val,
      priority: this.opsMap[val]
    })
    exp = exp.substring(i)
  }
  return exp
}

ConditionParser.prototype.readLogic = function (exp, tokens) {
  let i = 0
  let char = exp[i]
  const list = [char]
  if (['|', '&'].includes(char)) {
    char = exp[i + 1]
    if (['|', '&'].includes(char) && char === list[0]) {
      list.push(char)
      i += 2
      const val = list.join('')
      tokens.push({
        type: 'op',
        value: val,
        priority: this.opsMap[val]
      })
    } else {
      i += 1
    }
    exp = exp.substring(i)
  }
  return exp
}

ConditionParser.prototype.readNumber = function (exp, tokens) {
  if (/^(\d+(\.\d+)?)/.test(exp)) {
    const numVal = RegExp.$1
    tokens.push({
      type: 'number',
      value: numVal
    })
    exp = exp.substring(numVal.length)
  }
  return exp
}

ConditionParser.prototype.readOp = function (exp, tokens) {
  const char = exp[0]
  tokens.push({
    type: 'op',
    value: char,
    priority: this.opsMap[char]
  })
  exp = exp.substring(1)
  return exp
}

ConditionParser.prototype.readCurly = function (exp, tokens) {
  const char = exp[0]
  tokens.push({
    type: 'curly',
    value: char,
    priority: this.opsMap[char]
  })
  exp = exp.substring(1)
  return exp
}

ConditionParser.prototype.readString = function (exp, tokens) {
  if (/\s*(['"])([^'"]*)\1/.test(exp)) {
    tokens.push({
      type: 'string',
      value: RegExp.$2
    })
    exp = exp.substring(RegExp['$&'].length)
  } else if (/\s*([^ +\-*/%><=&|]+)/.test(exp)) {
    tokens.push({
      type: 'string',
      value: RegExp.$1
    })
    exp = exp.substring(RegExp['$&'].length)
  } else {
    exp = exp.substring(1)
  }
  return exp
}

ConditionParser.prototype.getToken = function (exp) {
  this.i = 0
  if (!exp) {
    return []
  }
  const tokens = []
  let placeholder = []
  while (exp.length) {
    const char = exp[0]
    if (char == '#') {
      exp = this.readPlaceholder(exp, tokens)
    } else if (['>', '<', '!'].includes(char)) {
      exp = this.readCompareOp(exp, tokens)
    } else if (['|', '&'].includes(char)) {
      exp = this.readLogic(exp, tokens)
    } else if (/\d/.test(char)) {
      exp = this.readNumber(exp, tokens)
    } else if (this.ops.includes(char)) {
      exp = this.readOp(exp, tokens)
    } else if (['(', ')'].includes(char)) {
      exp = this.readCurly(exp, tokens)
    } else if (!/\s/.test(char)) {
      exp = this.readString(exp, tokens)
    } else {
      exp = exp.substring(1)
    }
  }
  return tokens
}

ConditionParser.prototype.transform = function (tokens) {
  const results = []
  const opStack = []
  let i = 0
  while (i < tokens.length) {
    const token = tokens[i]
    if (['placeholder', 'number', 'string'].includes(token.type)) {
      results.push(token)
    } else if (this.ops.includes(token.value)) {
      let top = opStack[opStack.length - 1]
      while (top && top.priority >= token.priority) {
        results.push(top)
        opStack.pop()
        top = opStack[opStack.length - 1]
      }
      opStack.push(token)
    } else if (token.type === 'curly') {
      if (token.value === '(') {
        opStack.push(token)
      } else {
        let top = opStack.pop()
        while (top && top.value !== '(') {
          results.push(top)
          top = opStack.pop()
        }
        if (!top) {
          return null
        }
      }
    }
    i += 1
  }
  if (opStack.length) {
    let top = opStack.pop()
    while (top) {
      if (top.type === 'curly') {
        return null
      }
      results.push(top)
      top = opStack.pop()
    }
  }
  return results
}

ConditionParser.prototype.getKeyPathValue = function (keypath, obj) {
  const keys = (keypath || '').split('.')
  const val = keys.reduce((prev, key) => {
    return prev && prev[key]
  }, obj)
  return val
}

ConditionParser.prototype.compute = function (tokens, obj) {
  const newTokens = [...tokens]
  let result = []
  while (newTokens.length) {
    const token = newTokens.shift()
    if (token.type === 'placeholder') {
      let val = this.getKeyPathValue(token.value, obj)
      let other = token.includes
      if (other) {
        if (this.PHReg.test(other)) {
          other = this.getKeyPathValue(RegExp.$1, obj)
        } else if (/(['"])(.*)\1/.test(other)) {
          other = RegExp.$2
        }
        if (!val.map) {
          val = [val]
        }
        val = val.some(i => i == other)
      }
      result.push(val)
    } else if (['number', 'string'].includes(token.type)) {
      result.push(token.value)
    } else if (result.length >= 2) {
      let right = result.pop()
      let left = result.pop()
      const val = this.calc(left, right, token.value)
      if (val == null) {
        return null
      }
      result.push(val)
    } else {
      return null
    }
  }
  if (result.length > 1) {
    return null
  }
  const value = result[0]
  return value
}

ConditionParser.prototype.calc = function (a, b, op) {
  const isNumber = val => {
    if (/^[+-]?\d+(\.\d+)?$/.test(val)) {
      return true
    }
    return false
  }

  const needNumber = ['+', '-', '*', '/', '%', '>', '>=', '<', '<='].includes(op)
  if (needNumber) {
    if (!isNumber(a) || !isNumber(b)) {
      return null
    }
    a = Number(a)
    b = Number(b)
  }
  if (op === '*') {
    return a * b
  } else if (op === '/') {
    if (b > 0) {
      return a / b
    } else {
      return null
    }
  } else if (op === '%') {
    return a % b
  } else if (op === '+') {
    return a + b
  } else if (op === '-') {
    return a - b
  } else if (op === '>') {
    return a > b
  } else if (op === '>=') {
    return a >= b
  } else if (op === '<') {
    return a < b
  } else if (op === '<=') {
    return a <= b
  } else if (op === '&&') {
    return a && b
  } else if (op === '||') {
    return a || b
  } else if (op === '=') {
    return a == b
  } else if (op === '!=') {
    return a != b
  }
  return null
}
const parser = new ConditionParser()

const condition1 = `#{name} = lw1t@364.ccom && #age >= 234`
const formModel1 = {
    age: 234,
    name: 'lw1t@364.ccom'
}
let result = parser.eval(condition1, formModel1)

console.log(`测试1:`, result)

const condition2 = `#{roles}.includes('mana1ger') && #price > 1000`
const formModel2 = {
    roles: ['admin', 'manager', 'normal'],
    price: 1200
}
result = parser.eval(condition2, formModel2)

console.log(`测试2结果:`, result)

const condition3 = `#{price} * #{ratio}  >= 300`
const formModel3 = {
    price: 150,
    ratio: 2
}

result = parser.eval(condition3, formModel3)
console.log(`测试3结果:`, result)
console 命令行工具 X clear

                    
>
console