perf: introduce AST to avoid reparse

This commit is contained in:
harttle
2020-03-15 02:51:25 +08:00
committed by Jun Yang
parent 3b58f1c3f6
commit d2d6a38235
96 changed files with 1553 additions and 1168 deletions
+98 -42
View File
@@ -4,59 +4,115 @@ import { Context } from '../../../src/context/context'
import { toThenable } from '../../../src/util/async'
describe('Expression', function () {
let ctx: Context
beforeEach(function () {
ctx = new Context({
one: 1,
two: 2,
empty: '',
quote: '"',
space: ' ',
x: 'XXX',
y: undefined,
z: null,
obj: {
']': 'right bracket'
}
})
})
const ctx = new Context({})
it('should throw when context not defined', done => {
toThenable(new Expression().value(undefined!)).catch(err => {
expect(err.message).to.match(/context not defined/)
done()
return 0 as any
toThenable(new Expression('foo').value(undefined!))
.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 toThenable(new Expression('2.4').value(ctx))).to.equal(2.4)
expect(await toThenable(new Expression('"foo"').value(ctx))).to.equal('foo')
expect(await toThenable(new Expression('false').value(ctx))).to.equal(false)
})
it('should eval range expression', async function () {
const ctx = new Context({ two: 2 })
expect(await toThenable(new Expression('(2..4)').value(ctx))).to.deep.equal([2, 3, 4])
expect(await toThenable(new Expression('(two..4)').value(ctx))).to.deep.equal([2, 3, 4])
})
it('should eval literal', async function () {
expect(await toThenable(new Expression('2.4').value(ctx))).to.equal(2.4)
expect(await toThenable(new Expression('"foo"').value(ctx))).to.equal('foo')
expect(await toThenable(new Expression('false').value(ctx))).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 toThenable(new Expression('foo.bar').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('foo["bar"]').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('foo[coo]').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('foo[doo.foo]').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('foo[doo["foo"]]').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('doo[coo].foo').value(ctx))).to.equal('bar')
})
})
it('should eval simple expression', async function () {
expect(await toThenable(new Expression('1==2').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('1<2').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('1 < 2').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('1 < 2').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('2 <= 2').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('one <= two').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('x contains "x"').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('x contains "X"').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('1 contains "x"').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('y contains "x"').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('z contains "x"').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('(1..5) contains 3').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('(1..5) contains 6').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('"<=" == "<="').value(ctx))).to.equal(true)
describe('simple expression', function () {
it('should return false for "1==2"', async () => {
expect(await toThenable(new Expression('1==2').value(ctx))).to.equal(false)
})
it('should return true for "1<2"', async () => {
expect(await toThenable(new Expression('1<2').value(ctx))).to.equal(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toThenable(new Expression('1 < 2').value(ctx))).to.equal(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toThenable(new Expression('1 < 2').value(ctx))).to.equal(true)
})
it('should return true for "2 <= 2"', async () => {
expect(await toThenable(new Expression('2 <= 2').value(ctx))).to.equal(true)
})
it('should return true for "one <= two"', async () => {
const ctx = new Context({ one: 1, two: 2 })
expect(await toThenable(new Expression('one <= two').value(ctx))).to.equal(true)
})
it('should return false for "x contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('x contains "x"').value(ctx))).to.equal(false)
})
it('should return true for "x contains "X""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('x contains "X"').value(ctx))).to.equal(true)
})
it('should return false for "1 contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('1 contains "x"').value(ctx))).to.equal(false)
})
it('should return false for "y contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('y contains "x"').value(ctx))).to.equal(false)
})
it('should return false for "z contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('z contains "x"').value(ctx))).to.equal(false)
})
it('should return true for "(1..5) contains 3"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('(1..5) contains 3').value(ctx))).to.equal(true)
})
it('should return false for "(1..5) contains 6"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('(1..5) contains 6').value(ctx))).to.equal(false)
})
it('should return true for ""<=" == "<=""', async () => {
expect(await toThenable(new Expression('"<=" == "<="').value(ctx))).to.equal(true)
})
})
it('should allow space in quoted value', async function () {
const ctx = new Context({ space: ' ' })
expect(await toThenable(new Expression('" " == space').value(ctx))).to.equal(true)
})
describe('escape', () => {
it('should escape quote', async function () {
const ctx = new Context({ quote: '"' })
expect(await toThenable(new Expression('"\\"" == quote').value(ctx))).to.equal(true)
})
it('should escape square bracket', async function () {
expect(await toThenable(new Expression('obj["]"] == "right bracket"').value(ctx))).to.equal(true)
const ctx = new Context({ obj: { ']': 'bracket' } })
expect(await toThenable(new Expression('obj["]"] == "bracket"').value(ctx))).to.equal(true)
})
})
@@ -71,6 +127,7 @@ describe('Expression', function () {
expect(await toThenable(new Expression('1 < 2 or x contains "x"').value(ctx))).to.equal(true)
})
it('should support value and !=', async function () {
const ctx = new Context({ empty: '' })
expect(await toThenable(new Expression('empty and empty != ""').value(ctx))).to.equal(false)
})
it('should recognize quoted value', async function () {
@@ -84,10 +141,9 @@ describe('Expression', function () {
const ctx = new Context({ obj: { foo: true } })
expect(await toThenable(new Expression('obj["foo"] and true').value(ctx))).to.equal(true)
})
})
it('should eval range expression', async function () {
expect(await toThenable(new Expression('(2..4)').value(ctx))).to.deep.equal([2, 3, 4])
expect(await toThenable(new Expression('(two..4)').value(ctx))).to.deep.equal([2, 3, 4])
it('should allow nested property access', async function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } })
expect(await toThenable(new Expression('obj[keys["what\'s this"]]').value(ctx))).to.equal('FOO')
})
})
})
+2 -2
View File
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { Context } from '../../../src/context/context'
import { Token } from '../../../src/parser/token'
import { HTMLToken } from '../../../src/tokens/html-token'
import { Render } from '../../../src/render/render'
import { HTML } from '../../../src/template/html'
import { toThenable } from '../../../src/util/async'
@@ -14,7 +14,7 @@ describe('render', function () {
describe('.renderTemplates()', function () {
it('should render html', async function () {
const scope = new Context()
const token = { content: '<p>' } as Token
const token = { getContent: () => '<p>' } as HTMLToken
const html = await toThenable(render.renderTemplates([new HTML(token)], scope))
return expect(html).to.equal('<p>')
})