/
expression.ts
182 lines (173 loc) · 8.58 KB
/
expression.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import { Tokenizer } from '../../../src/parser/tokenizer'
import { expect } from 'chai'
import { Drop } from '../../../src/drop/drop'
import { Context } from '../../../src/context/context'
import { toPromise, toValueSync } from '../../../src/util/async'
describe('Expression', function () {
const ctx = new Context({})
const create = (str: string) => new Tokenizer(str).readExpression()
it('should throw when context not defined', done => {
toPromise(create('foo').evaluate(undefined!, false))
.then(() => done(new Error('should not resolved')))
.catch(err => {
expect(err.message).to.match(/context not defined/)
done()
})
})
describe('single value', function () {
it('should eval literal', async function () {
expect(await toPromise(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).to.equal('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).to.equal(false)
})
it('should eval range expression', async function () {
const ctx = new Context({ two: 2 })
expect(await toPromise(create('(2..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4])
expect(await toPromise(create('(two..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4])
})
it('should eval literal', async function () {
expect(await toPromise(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).to.equal('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).to.equal(false)
})
it('should eval property access', async function () {
const ctx = new Context({
foo: { bar: 'BAR' },
coo: 'bar',
doo: { foo: 'bar', bar: { foo: 'bar' } }
})
expect(await toPromise(create('foo.bar').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo["bar"]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[coo]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[doo.foo]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[doo["foo"]]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('doo[coo].foo').evaluate(ctx, false))).to.equal('bar')
})
})
describe('simple expression', function () {
it('should return false for "1==2"', async () => {
expect(await toPromise(create('1==2').evaluate(ctx, false))).to.equal(false)
})
it('should return true for "1<2"', async () => {
expect(await toPromise(create('1<2').evaluate(ctx, false))).to.equal(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toPromise(create('1 < 2').evaluate(ctx, false))).to.equal(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toPromise(create('1 < 2').evaluate(ctx, false))).to.equal(true)
})
it('should return true for "2 <= 2"', async () => {
expect(await toPromise(create('2 <= 2').evaluate(ctx, false))).to.equal(true)
})
it('should return true for "one <= two"', async () => {
const ctx = new Context({ one: 1, two: 2 })
expect(await toPromise(create('one <= two').evaluate(ctx, false))).to.equal(true)
})
it('should return false for "x contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('x contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should return true for "x contains "X""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('x contains "X"').evaluate(ctx, false))).to.equal(true)
})
it('should return false for "1 contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('1 contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should return false for "y contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('y contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should return false for "z contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('z contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should return true for "(1..5) contains 3"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('(1..5) contains 3').evaluate(ctx, false))).to.equal(true)
})
it('should return false for "(1..5) contains 6"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('(1..5) contains 6').evaluate(ctx, false))).to.equal(false)
})
it('should return true for ""<=" == "<=""', async () => {
expect(await toPromise(create('"<=" == "<="').evaluate(ctx, false))).to.equal(true)
})
})
it('should allow space in quoted value', async function () {
const ctx = new Context({ space: ' ' })
expect(await toPromise(create('" " == space').evaluate(ctx, false))).to.equal(true)
})
describe('escape', () => {
it('should escape quote', async function () {
const ctx = new Context({ quote: '"' })
expect(await toPromise(create('"\\"" == quote').evaluate(ctx, false))).to.equal(true)
})
it('should escape square bracket', async function () {
const ctx = new Context({ obj: { ']': 'bracket' } })
expect(await toPromise(create('obj["]"] == "bracket"').evaluate(ctx, false))).to.equal(true)
})
})
describe('complex expression', function () {
it('should support value or value', async function () {
expect(await toPromise(create('false or true').evaluate(ctx, false))).to.equal(true)
})
it('should support < and contains', async function () {
expect(await toPromise(create('1 < 2 and x contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should support < or contains', async function () {
expect(await toPromise(create('1 < 2 or x contains "x"').evaluate(ctx, false))).to.equal(true)
})
it('should support Drops for "x contains "x""', async () => {
class TemplateDrop extends Drop {
valueOf () { return 'X' }
}
const ctx = new Context({ x: 'XXX', X: new TemplateDrop() })
expect(await toPromise(create('x contains X').evaluate(ctx, false))).to.equal(true)
})
it('should support value and !=', async function () {
const ctx = new Context({ empty: '' })
expect(await toPromise(create('empty and empty != ""').evaluate(ctx, false))).to.equal(false)
})
it('should recognize quoted value', async function () {
expect(await toPromise(create('">"').evaluate(ctx, false))).to.equal('>')
})
it('should evaluate from right to left', async function () {
expect(await toPromise(create('true or false and false').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('true and false and false or true').evaluate(ctx, false))).to.equal(false)
})
it('should recognize property access', async function () {
const ctx = new Context({ obj: { foo: true } })
expect(await toPromise(create('obj["foo"] and true').evaluate(ctx, false))).to.equal(true)
})
it('should allow nested property access', async function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } })
expect(await toPromise(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).to.equal('FOO')
})
it('should support not', async function () {
expect(await toPromise(create('not 1 < 2').evaluate(ctx))).to.equal(false)
})
it('not should have higher precedence than and/or', async function () {
expect(await toPromise(create('not 1 < 2 or not 1 > 2').evaluate(ctx))).to.equal(true)
expect(await toPromise(create('not 1 < 2 and not 1 > 2').evaluate(ctx))).to.equal(false)
})
})
describe('sync', function () {
it('should eval literal', function () {
expect(toValueSync(create('2.4').evaluate(ctx, false))).to.equal(2.4)
})
it('should return false for "1==2"', () => {
expect(toValueSync(create('1==2').evaluate(ctx, false))).to.equal(false)
})
it('should escape quote', function () {
const ctx = new Context({ quote: '"' })
expect(toValueSync(create('"\\"" == quote').evaluate(ctx, false))).to.equal(true)
})
it('should allow nested property access', function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } })
expect(toValueSync(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).to.equal('FOO')
})
})
})