mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 04:40:39 -07:00
feat: create trie programmatically in options
This commit is contained in:
committed by
Jun Yang
parent
8734e2e6ce
commit
befc33c4eb
@@ -2,7 +2,7 @@ import { Tokenizer, assert, TagImplOptions, TagToken, Context } from '../../type
|
||||
|
||||
export default {
|
||||
parse: function (token: TagToken) {
|
||||
const tokenizer = new Tokenizer(token.args)
|
||||
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
|
||||
this.key = tokenizer.readIdentifier().content
|
||||
tokenizer.skipBlank()
|
||||
assert(tokenizer.peek() === '=', () => `illegal token ${token.getText()}`)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { evalQuotedToken } from '../../render/expression'
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||
const tokenizer = new Tokenizer(tagToken.args)
|
||||
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie)
|
||||
this.variable = readVariableName(tokenizer)
|
||||
assert(this.variable, () => `${tagToken.args} not valid identifier`)
|
||||
|
||||
|
||||
@@ -26,10 +26,11 @@ export default {
|
||||
|
||||
render: function * (ctx: Context, emitter: Emitter) {
|
||||
const r = this.liquid.renderer
|
||||
const cond = yield new Expression(this.cond, this.liquid.options.operators).value(ctx)
|
||||
const { operators, operatorsTrie } = this.liquid.options
|
||||
const cond = yield new Expression(this.cond, operators, operatorsTrie).value(ctx)
|
||||
for (let i = 0; i < this.cases.length; i++) {
|
||||
const branch = this.cases[i]
|
||||
const val = yield new Expression(branch.val, this.liquid.options.operators).value(ctx)
|
||||
const val = yield new Expression(branch.val, operators, operatorsTrie).value(ctx)
|
||||
if (val === cond) {
|
||||
yield r.renderTemplates(branch.templates, ctx, emitter)
|
||||
return
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Tokenizer } from '../../parser/tokenizer'
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken) {
|
||||
const tokenizer = new Tokenizer(tagToken.args)
|
||||
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie)
|
||||
const group = tokenizer.readValue()
|
||||
tokenizer.skipBlank()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isNumber, stringify } from '../../util/underscore'
|
||||
|
||||
export default {
|
||||
parse: function (token: TagToken) {
|
||||
const tokenizer = new Tokenizer(token.args)
|
||||
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
|
||||
this.variable = tokenizer.readIdentifier().content
|
||||
},
|
||||
render: function (context: Context, emitter: Emitter) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Hash } from '../../template/tag/hash'
|
||||
export default {
|
||||
type: 'block',
|
||||
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
|
||||
const toknenizer = new Tokenizer(token.args)
|
||||
const toknenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
|
||||
|
||||
const variable = toknenizer.readIdentifier()
|
||||
const inStr = toknenizer.readIdentifier()
|
||||
|
||||
@@ -29,9 +29,10 @@ export default {
|
||||
|
||||
render: function * (ctx: Context, emitter: Emitter) {
|
||||
const r = this.liquid.renderer
|
||||
const { operators, operatorsTrie } = this.liquid.options
|
||||
|
||||
for (const branch of this.branches) {
|
||||
const cond = yield new Expression(branch.cond, this.liquid.options.operators, ctx.opts.lenientIf).value(ctx)
|
||||
const cond = yield new Expression(branch.cond, operators, operatorsTrie, ctx.opts.lenientIf).value(ctx)
|
||||
if (isTruthy(cond, ctx)) {
|
||||
yield r.renderTemplates(branch.templates, ctx, emitter)
|
||||
return
|
||||
|
||||
@@ -4,7 +4,7 @@ import BlockMode from '../../context/block-mode'
|
||||
export default {
|
||||
parse: function (token: TagToken) {
|
||||
const args = token.args
|
||||
const tokenizer = new Tokenizer(args)
|
||||
const tokenizer = new Tokenizer(args, this.liquid.options.operatorsTrie)
|
||||
this.file = this.liquid.options.dynamicPartials
|
||||
? tokenizer.readValue()
|
||||
: tokenizer.readFileName()
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Tokenizer, Emitter, TagToken, Context, TagImplOptions } from '../../typ
|
||||
|
||||
export default {
|
||||
parse: function (token: TagToken) {
|
||||
const tokenizer = new Tokenizer(token.args)
|
||||
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
|
||||
this.variable = tokenizer.readIdentifier().content
|
||||
},
|
||||
render: function (context: Context, emitter: Emitter) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import BlockMode from '../../context/block-mode'
|
||||
|
||||
export default {
|
||||
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
|
||||
const tokenizer = new Tokenizer(token.args)
|
||||
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
|
||||
const file = this.liquid.options.dynamicPartials ? tokenizer.readValue() : tokenizer.readFileName()
|
||||
assert(file, () => `illegal argument "${token.args}"`)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { evalQuotedToken, TypeGuards, Tokenizer, evalToken, Hash, Emitter, TagTo
|
||||
export default {
|
||||
parse: function (token: TagToken) {
|
||||
const args = token.args
|
||||
const tokenizer = new Tokenizer(args)
|
||||
const tokenizer = new Tokenizer(args, this.liquid.options.operatorsTrie)
|
||||
this.file = this.liquid.options.dynamicPartials
|
||||
? tokenizer.readValue()
|
||||
: tokenizer.readFileName()
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Tokenizer } from '../../parser/tokenizer'
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||
const tokenizer = new Tokenizer(tagToken.args)
|
||||
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie)
|
||||
|
||||
this.variable = tokenizer.readIdentifier()
|
||||
tokenizer.skipBlank()
|
||||
|
||||
@@ -29,7 +29,8 @@ export default {
|
||||
|
||||
render: function * (ctx: Context, emitter: Emitter) {
|
||||
const r = this.liquid.renderer
|
||||
const cond = yield new Expression(this.cond, this.liquid.options.operators, ctx.opts.lenientIf).value(ctx)
|
||||
const { operators, operatorsTrie } = this.liquid.options
|
||||
const cond = yield new Expression(this.cond, operators, operatorsTrie, ctx.opts.lenientIf).value(ctx)
|
||||
|
||||
if (isFalsy(cond, ctx)) {
|
||||
yield r.renderTemplates(this.templates, ctx, emitter)
|
||||
@@ -37,7 +38,7 @@ export default {
|
||||
}
|
||||
|
||||
for (const branch of this.branches) {
|
||||
const cond = yield new Expression(branch.cond, this.liquid.options.operators, ctx.opts.lenientIf).value(ctx)
|
||||
const cond = yield new Expression(branch.cond, operators, operatorsTrie, ctx.opts.lenientIf).value(ctx)
|
||||
if (isTruthy(cond, ctx)) {
|
||||
yield r.renderTemplates(branch.templates, ctx, emitter)
|
||||
return
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Cache } from './cache/cache'
|
||||
import { LRU } from './cache/lru'
|
||||
import { FS } from './fs/fs'
|
||||
import { defaultOperators, Operators } from './render/operator'
|
||||
import { createTrie, Trie } from './util/operator-trie'
|
||||
|
||||
export interface LiquidOptions {
|
||||
/** A directory or an array of directories from where to resolve layout and include templates, and the filename passed to `.renderFile()`. If it's an array, the files are looked up in the order they occur in the array. Defaults to `["."]` */
|
||||
@@ -55,6 +56,7 @@ export interface LiquidOptions {
|
||||
interface NormalizedOptions extends LiquidOptions {
|
||||
root?: string[];
|
||||
cache?: Cache<Template[]>;
|
||||
operatorsTrie?: Trie;
|
||||
}
|
||||
|
||||
export interface NormalizedFullOptions extends NormalizedOptions {
|
||||
@@ -79,6 +81,7 @@ export interface NormalizedFullOptions extends NormalizedOptions {
|
||||
globals: object;
|
||||
keepOutputType: boolean;
|
||||
operators: Operators;
|
||||
operatorsTrie: Trie;
|
||||
}
|
||||
|
||||
export const defaultOptions: NormalizedFullOptions = {
|
||||
@@ -102,7 +105,8 @@ export const defaultOptions: NormalizedFullOptions = {
|
||||
lenientIf: false,
|
||||
globals: {},
|
||||
keepOutputType: false,
|
||||
operators: defaultOperators
|
||||
operators: defaultOperators,
|
||||
operatorsTrie: createTrie(defaultOperators)
|
||||
}
|
||||
|
||||
export function normalize (options?: LiquidOptions): NormalizedOptions {
|
||||
@@ -117,6 +121,9 @@ export function normalize (options?: LiquidOptions): NormalizedOptions {
|
||||
else cache = options.cache ? new LRU<Template[]>(1024) : undefined
|
||||
options.cache = cache
|
||||
}
|
||||
if (options.hasOwnProperty('operators')) {
|
||||
(options as NormalizedOptions).operatorsTrie = createTrie(options.operators!)
|
||||
}
|
||||
return options as NormalizedOptions
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ export class Liquid {
|
||||
forOwn(builtinFilters, (handler: FilterImplOptions, name: string) => this.registerFilter(snakeCase(name), handler))
|
||||
}
|
||||
public parse (html: string, filepath?: string): Template[] {
|
||||
const tokenizer = new Tokenizer(html, filepath)
|
||||
const tokenizer = new Tokenizer(html, this.options.operatorsTrie, filepath)
|
||||
const tokens = tokenizer.readTopLevelTokens(this.options)
|
||||
return this.parser.parse(tokens)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { IDENTIFIER } from '../util/character'
|
||||
import { Trie } from '../util/operator-trie'
|
||||
|
||||
const trie = {
|
||||
a: { n: { d: { end: true, needBoundary: true } } },
|
||||
o: { r: { end: true, needBoundary: true } },
|
||||
c: { o: { n: { t: { a: { i: { n: { s: { end: true, needBoundary: true } } } } } } } },
|
||||
'=': { '=': { end: true } },
|
||||
'!': { '=': { end: true } },
|
||||
'>': { end: true, '=': { end: true } },
|
||||
'<': { end: true, '=': { end: true } }
|
||||
}
|
||||
|
||||
export function matchOperator (str: string, begin: number, end = str.length) {
|
||||
export function matchOperator (str: string, begin: number, trie: Trie, end = str.length) {
|
||||
let node = trie
|
||||
let i = begin
|
||||
let info
|
||||
|
||||
@@ -22,6 +22,7 @@ import { TokenizationError } from '../util/error'
|
||||
import { NormalizedFullOptions, defaultOptions } from '../liquid-options'
|
||||
import { TYPES, QUOTE, BLANK, IDENTIFIER } from '../util/character'
|
||||
import { matchOperator } from './match-operator'
|
||||
import { Trie } from '../util/operator-trie'
|
||||
|
||||
export class Tokenizer {
|
||||
p = 0
|
||||
@@ -30,6 +31,7 @@ export class Tokenizer {
|
||||
|
||||
constructor (
|
||||
private input: string,
|
||||
private trie: Trie,
|
||||
private file: string = ''
|
||||
) {
|
||||
this.N = input.length
|
||||
@@ -54,7 +56,7 @@ export class Tokenizer {
|
||||
}
|
||||
readOperator (): OperatorToken | undefined {
|
||||
this.skipBlank()
|
||||
const end = matchOperator(this.input, this.p, this.p + 8)
|
||||
const end = matchOperator(this.input, this.p, this.trie, this.p + 8)
|
||||
if (end === -1) return
|
||||
return new OperatorToken(this.input, this.p, (this.p = end), this.file)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { range, toValue } from '../util/underscore'
|
||||
import { Tokenizer } from '../parser/tokenizer'
|
||||
import { Operators } from '../render/operator'
|
||||
import { UndefinedVariableError, InternalUndefinedVariableError } from '../util/error'
|
||||
import { Trie } from '../util/operator-trie'
|
||||
|
||||
export class Expression {
|
||||
private operands: any[] = []
|
||||
@@ -20,8 +21,8 @@ export class Expression {
|
||||
private lenient: boolean
|
||||
private operators: Operators
|
||||
|
||||
public constructor (str: string, operators: Operators, lenient = false) {
|
||||
const tokenizer = new Tokenizer(str)
|
||||
public constructor (str: string, operators: Operators, operatorsTrie: Trie, lenient = false) {
|
||||
const tokenizer = new Tokenizer(str, operatorsTrie)
|
||||
this.postfix = [...toPostfix(tokenizer.readExpression())]
|
||||
this.lenient = lenient
|
||||
this.operators = operators
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Tokenizer } from '../../parser/tokenizer'
|
||||
export class Hash {
|
||||
hash: { [key: string]: any } = {}
|
||||
constructor (markup: string) {
|
||||
const tokenizer = new Tokenizer(markup)
|
||||
const tokenizer = new Tokenizer(markup, {})
|
||||
for (const hash of tokenizer.readHashes()) {
|
||||
this.hash[hash.name.content] = hash.value
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export class Value {
|
||||
* @param str the value to be valuated, eg.: "foobar" | truncate: 3
|
||||
*/
|
||||
public constructor (str: string, private readonly filterMap: FilterMap, liquid: Liquid) {
|
||||
const tokenizer = new Tokenizer(str)
|
||||
const tokenizer = new Tokenizer(str, liquid.options.operatorsTrie)
|
||||
this.initial = tokenizer.readValue()
|
||||
this.filters = tokenizer.readFilters().map(({ name, args }) => new Filter(name, this.filterMap.get(name), args, liquid))
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export class TagToken extends DelimitedToken {
|
||||
const value = input.slice(begin + tagDelimiterLeft.length, end - tagDelimiterRight.length)
|
||||
super(TokenKind.Tag, value, input, begin, end, trimTagLeft, trimTagRight, file)
|
||||
|
||||
const tokenizer = new Tokenizer(this.content)
|
||||
const tokenizer = new Tokenizer(this.content, options.operatorsTrie)
|
||||
this.name = tokenizer.readIdentifier().getText()
|
||||
if (!this.name) throw new TokenizationError(`illegal tag syntax`, this)
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Operators } from '../render/operator'
|
||||
|
||||
export interface Trie {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function createTrie (operators: Operators): Trie {
|
||||
const trie: Trie = {}
|
||||
for (const [name, handler] of Object.entries(operators)) {
|
||||
let node = trie
|
||||
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
const c = name[i]
|
||||
node[c] = node[c] || {}
|
||||
|
||||
if (i === name.length - 1 && c !== '=') {
|
||||
node[c].needBoundary = true
|
||||
}
|
||||
|
||||
node = node[c]
|
||||
}
|
||||
|
||||
node.handler = handler
|
||||
node.end = true
|
||||
}
|
||||
return trie
|
||||
}
|
||||
Reference in New Issue
Block a user