feat: nil/null/empty/blank literals, resolves #102

This commit is contained in:
harttle
2019-02-24 21:50:01 +08:00
parent 54b2adce12
commit 88c9e96392
19 changed files with 355 additions and 52 deletions
+58 -16
View File
@@ -2,14 +2,43 @@ import * as lexical from '../parser/lexical'
import assert from '../util/assert'
import Scope from 'src/scope/scope'
import { range, last } from 'src/util/underscore'
import { isComparable } from 'src/drop/icomparable'
import { NullDrop } from 'src/drop/null-drop'
import { EmptyDrop } from 'src/drop/empty-drop'
import { BlankDrop } from 'src/drop/blank-drop'
import { isDrop } from 'src/drop/idrop'
const operators = {
'==': (l: any, r: any) => l === r,
'!=': (l: any, r: any) => l !== r,
'>': (l: any, r: any) => l !== null && r !== null && l > r,
'<': (l: any, r: any) => l !== null && r !== null && l < r,
'>=': (l: any, r: any) => l !== null && r !== null && l >= r,
'<=': (l: any, r: any) => l !== null && r !== null && l <= r,
const binaryOperators: {[key: string]: (lhs: any, rhs: any) => boolean} = {
'==': (l: any, r: any) => {
if (isComparable(l)) return l.equals(r)
if (isComparable(r)) return r.equals(l)
return l === r
},
'!=': (l: any, r: any) => {
if (isComparable(l)) return !l.equals(r)
if (isComparable(r)) return !r.equals(l)
return l !== r
},
'>': (l: any, r: any) => {
if (isComparable(l)) return l.gt(r)
if (isComparable(r)) return r.lt(l)
return l > r
},
'<': (l: any, r: any) => {
if (isComparable(l)) return l.lt(r)
if (isComparable(r)) return r.gt(l)
return l < r
},
'>=': (l: any, r: any) => {
if (isComparable(l)) return l.geq(r)
if (isComparable(r)) return r.leq(l)
return l >= r
},
'<=': (l: any, r: any) => {
if (isComparable(l)) return l.leq(r)
if (isComparable(r)) return r.geq(l)
return l <= r
},
'contains': (l: any, r: any) => {
if (!l) return false
if (typeof l.indexOf !== 'function') return false
@@ -19,41 +48,54 @@ const operators = {
'or': (l: any, r: any) => isTruthy(l) || isTruthy(r)
}
export function evalExp (exp: string, scope: Scope): any {
assert(scope, 'unable to evalExp: scope undefined')
export function parseExp (exp: string, scope: Scope): any {
assert(scope, 'unable to parseExp: scope undefined')
const operatorREs = lexical.operators
let match
for (let i = 0; i < operatorREs.length; i++) {
const operatorRE = operatorREs[i]
const expRE = new RegExp(`^(${lexical.quoteBalanced.source})(${operatorRE.source})(${lexical.quoteBalanced.source})$`)
if ((match = exp.match(expRE))) {
const l = evalExp(match[1], scope)
const op = operators[match[2].trim()]
const r = evalExp(match[3], scope)
const l = parseExp(match[1], scope)
const op = binaryOperators[match[2].trim()]
const r = parseExp(match[3], scope)
return op(l, r)
}
}
if ((match = exp.match(lexical.rangeLine))) {
const low = evalValue(match[1], scope)
const high = evalValue(match[2], scope)
const low = parseValue(match[1], scope)
const high = parseValue(match[2], scope)
return range(low, high + 1)
}
return evalValue(exp, scope)
return parseValue(exp, scope)
}
export function evalValue (str: string, scope: Scope) {
export function evalExp (str: string, scope: Scope): any {
const value = parseExp(str, scope)
return isDrop(value) ? value.value() : value
}
function parseValue (str: string, scope: Scope): any {
if (!str) return null
str = str.trim()
if (str === 'true') return true
if (str === 'false') return false
if (str === 'nil' || str === 'null') return new NullDrop()
if (str === 'empty') return new EmptyDrop()
if (str === 'blank') return new BlankDrop()
if (!isNaN(Number(str))) return Number(str)
if ((str[0] === '"' || str[0] === "'") && str[0] === last(str)) return str.slice(1, -1)
return scope.get(str)
}
export function evalValue (str: string, scope: Scope): any {
const value = parseValue(str, scope)
return isDrop(value) ? value.value() : value
}
export function isTruthy (val: any): boolean {
return !isFalsy(val)
}