mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 12:50:38 -07:00
perf: introduce AST to avoid reparse
This commit is contained in:
+60
-33
@@ -1,47 +1,87 @@
|
||||
import { QuotedToken } from '../tokens/quoted-token'
|
||||
import { NumberToken } from '../tokens/number-token'
|
||||
import { assert } from '../util/assert'
|
||||
import { rangeLine } from '../parser/lexical'
|
||||
import { parseLiteral } from '../parser/literal'
|
||||
import { literalValues } from '../util/literal'
|
||||
import { LiteralToken } from '../tokens/literal-token'
|
||||
import * as TypeGuards from '../util/type-guards'
|
||||
import { Token } from '../tokens/token'
|
||||
import { OperatorToken } from '../tokens/operator-token'
|
||||
import { RangeToken } from '../tokens/range-token'
|
||||
import { parseStringLiteral } from '../parser/parse-string-literal'
|
||||
import { Context } from '../context/context'
|
||||
import { range, toValue } from '../util/underscore'
|
||||
import { isOperator, precedence, operatorImpls } from './operator'
|
||||
import { Tokenizer } from '../parser/tokenizer'
|
||||
import { operatorImpls } from '../render/operator'
|
||||
|
||||
export class Expression {
|
||||
private operands: any[] = []
|
||||
private postfix: string[]
|
||||
private postfix: IterableIterator<Token>
|
||||
|
||||
public constructor (str = '') {
|
||||
public constructor (str: string) {
|
||||
const tokenizer = new Tokenizer(str)
|
||||
this.postfix = [...toPostfix(tokenizer.readExpression())]
|
||||
this.postfix = toPostfix(tokenizer.readExpression())
|
||||
}
|
||||
public * evaluate (ctx: Context): any {
|
||||
assert(ctx, 'unable to evaluate: context not defined')
|
||||
|
||||
public evaluate (ctx: Context): any {
|
||||
for (const token of this.postfix) {
|
||||
if (isOperator(token)) {
|
||||
if (TypeGuards.isOperatorToken(token)) {
|
||||
const r = this.operands.pop()
|
||||
const l = this.operands.pop()
|
||||
const result = operatorImpls[token](l, r)
|
||||
const result = evalOperatorToken(token, l, r)
|
||||
this.operands.push(result)
|
||||
} else if (isRange(token)) {
|
||||
this.operands.push(yield rangeValue(token, ctx))
|
||||
} else {
|
||||
const literal = parseLiteral(token)
|
||||
this.operands.push(literal !== undefined ? literal : yield ctx.get(token))
|
||||
this.operands.push(evalToken(token, ctx))
|
||||
}
|
||||
}
|
||||
return this.operands[0]
|
||||
}
|
||||
public * value (ctx: Context) {
|
||||
return toValue(yield this.evaluate(ctx))
|
||||
return toValue(this.evaluate(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
function * toPostfix (tokens: IterableIterator<string>): IterableIterator<string> {
|
||||
const ops = []
|
||||
export function evalToken (token: Token | undefined, ctx: Context): any {
|
||||
assert(ctx, () => 'unable to evaluate: context not defined')
|
||||
if (TypeGuards.isPropertyAccessToken(token)) {
|
||||
const variable = token.variable.getText()
|
||||
const props: string[] = token.props.map(prop => evalToken(prop, ctx))
|
||||
return ctx.get([variable, ...props])
|
||||
}
|
||||
if (TypeGuards.isRangeToken(token)) return evalRangeToken(token, ctx)
|
||||
if (TypeGuards.isLiteralToken(token)) return evalLiteralToken(token)
|
||||
if (TypeGuards.isNumberToken(token)) return evalNumberToken(token)
|
||||
if (TypeGuards.isWordToken(token)) return token.getText()
|
||||
if (TypeGuards.isQuotedToken(token)) return evalQuotedToken(token)
|
||||
}
|
||||
|
||||
function evalNumberToken (token: NumberToken) {
|
||||
const str = token.whole.content + '.' + (token.decimal ? token.decimal.content : '')
|
||||
return Number(str)
|
||||
}
|
||||
|
||||
export function evalQuotedToken (token: QuotedToken) {
|
||||
return parseStringLiteral(token.getText())
|
||||
}
|
||||
|
||||
function evalOperatorToken (token: OperatorToken, lhs: any, rhs: any) {
|
||||
const impl = operatorImpls[token.operator]
|
||||
return impl(lhs, rhs)
|
||||
}
|
||||
|
||||
function evalLiteralToken (token: LiteralToken) {
|
||||
return literalValues[token.literal]
|
||||
}
|
||||
|
||||
function evalRangeToken (token: RangeToken, ctx: Context) {
|
||||
const low: number = evalToken(token.lhs, ctx)
|
||||
const high: number = evalToken(token.rhs, ctx)
|
||||
return range(+low, +high + 1)
|
||||
}
|
||||
|
||||
function * toPostfix (tokens: IterableIterator<Token>): IterableIterator<Token> {
|
||||
const ops: OperatorToken[] = []
|
||||
for (const token of tokens) {
|
||||
if (isOperator(token)) {
|
||||
while (ops.length && precedence[ops[ops.length - 1]] > precedence[token]) {
|
||||
if (TypeGuards.isOperatorToken(token)) {
|
||||
while (ops.length && ops[ops.length - 1].getPrecedence() > token.getPrecedence()) {
|
||||
yield ops.pop()!
|
||||
}
|
||||
ops.push(token)
|
||||
@@ -51,16 +91,3 @@ function * toPostfix (tokens: IterableIterator<string>): IterableIterator<string
|
||||
yield ops.pop()!
|
||||
}
|
||||
}
|
||||
|
||||
function * rangeValue (token: string, ctx: Context) {
|
||||
let match
|
||||
if ((match = token.match(rangeLine))) {
|
||||
const low = yield new Expression(match[1]).value(ctx)
|
||||
const high = yield new Expression(match[2]).value(ctx)
|
||||
return range(+low, +high + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function isRange (str: string) {
|
||||
return rangeLine.test(str)
|
||||
}
|
||||
|
||||
@@ -2,18 +2,6 @@ import { isComparable } from '../drop/icomparable'
|
||||
import { isFunction } from '../util/underscore'
|
||||
import { isTruthy } from '../render/boolean'
|
||||
|
||||
export const precedence = {
|
||||
'==': 1,
|
||||
'!=': 1,
|
||||
'>': 1,
|
||||
'<': 1,
|
||||
'>=': 1,
|
||||
'<=': 1,
|
||||
'contains': 1,
|
||||
'and': 0,
|
||||
'or': 0
|
||||
}
|
||||
|
||||
export const operatorImpls: {[key: string]: (lhs: any, rhs: any) => boolean} = {
|
||||
'==': (l: any, r: any) => {
|
||||
if (isComparable(l)) return l.equals(r)
|
||||
@@ -51,7 +39,3 @@ export const operatorImpls: {[key: string]: (lhs: any, rhs: any) => boolean} = {
|
||||
'and': (l: any, r: any) => isTruthy(l) && isTruthy(r),
|
||||
'or': (l: any, r: any) => isTruthy(l) || isTruthy(r)
|
||||
}
|
||||
|
||||
export function isOperator (token: string) {
|
||||
return precedence.hasOwnProperty(token)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user