Skip to content

Commit

Permalink
Split file
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Mar 13, 2022
1 parent 9990acd commit 960ebef
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 123 deletions.
124 changes: 1 addition & 123 deletions src/config/normalize/lib/wild_wild_path/parsing/parse.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import {
ESCAPE,
PATH_SEPARATOR,
PATH_SEPARATOR_NAME,
TOKEN_SEPARATOR,
SPECIAL_CHARS,
} from '../tokens/escape.js'
import { getStringTokenType } from '../tokens/main.js'

import { normalizePaths, isQueryString } from './normalize.js'
import { parseQuery } from './query.js'

// Parse a query string into an array of tokens.
// Also validate and normalize it.
Expand Down Expand Up @@ -89,117 +81,3 @@ const safeParseQuery = function (query) {
throw new Error(`Invalid query "${query}": ${error.message}`)
}
}

// Use imperative logic for performance
// eslint-disable-next-line complexity
const parseQuery = function (query) {
const state = getInitialState()

// eslint-disable-next-line fp/no-loops
for (; state.index <= query.length; state.index += 1) {
const char = query[state.index]

// eslint-disable-next-line max-depth
if (char === ESCAPE) {
parseEscape(state, query)
} else if (char === TOKEN_SEPARATOR) {
addToken(state)
} else if (char === PATH_SEPARATOR || state.index === query.length) {
addPath(state)
} else {
state.chars += char
}
}

return state.paths
}

const getInitialState = function () {
const state = { paths: [], index: 0 }
resetPathState(state)
resetTokenState(state)
return state
}

const parseEscape = function (state, query) {
const nextChar = query[state.index + 1]

if (SPECIAL_CHARS.has(nextChar)) {
state.index += 1
state.chars += nextChar
return
}

if (state.chars.length !== 0) {
throw new Error(
`character "${ESCAPE}" must either be at the start of a token, or be followed by ${PATH_SEPARATOR_NAME} or ${TOKEN_SEPARATOR} or ${ESCAPE}`,
)
}

state.isProp = true
}

const addPath = function (state) {
if (hasNoPath(state)) {
return
}

if (!hasOnlyDots(state)) {
addToken(state)
}

// eslint-disable-next-line fp/no-mutating-methods
state.paths.push(state.path)
resetPathState(state)
}

// When the query is an empty string or when two spaces are consecutive
const hasNoPath = function (state) {
return state.firstToken && state.chars.length === 0 && state.path.length === 0
}

const resetPathState = function (state) {
state.path = []
state.firstToken = true
state.onlyDots = true
}

const addToken = function (state) {
if (handleLeadingDot(state)) {
return
}

state.onlyDots = hasOnlyDots(state)
const tokenType = getStringTokenType(state.chars, state.isProp)
const token = tokenType.normalize(tokenType.parse(state.chars))
// eslint-disable-next-line fp/no-mutating-methods
state.path.push(token)
resetTokenState(state)
}

// In principle, the root query should be an empty string.
// But we use a lone dot instead because:
// - It distinguishes it from an absence of query
// - It allows parsing it in the middle of a space-separated list of queries
// (as opposed to an empty string)
// However, we create ambiguities for queries with only dots (including a
// lone dot), where the last dot should not create an additional token.
const hasOnlyDots = function (state) {
return state.onlyDots && state.chars.length === 0
}

// We ignore leading dots, because they are used to represent the root.
// We do not require them for simplicity.
const handleLeadingDot = function (state) {
if (!state.firstToken) {
return false
}

state.firstToken = false
return state.chars.length === 0
}

const resetTokenState = function (state) {
state.isProp = false
state.chars = ''
}
122 changes: 122 additions & 0 deletions src/config/normalize/lib/wild_wild_path/parsing/query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
ESCAPE,
PATH_SEPARATOR,
PATH_SEPARATOR_NAME,
TOKEN_SEPARATOR,
SPECIAL_CHARS,
} from '../tokens/escape.js'
import { getStringTokenType } from '../tokens/main.js'

// Use imperative logic for performance
// eslint-disable-next-line complexity
export const parseQuery = function (query) {
const state = getInitialState()

// eslint-disable-next-line fp/no-loops
for (; state.index <= query.length; state.index += 1) {
const char = query[state.index]

// eslint-disable-next-line max-depth
if (char === ESCAPE) {
parseEscape(state, query)
} else if (char === TOKEN_SEPARATOR) {
addToken(state)
} else if (char === PATH_SEPARATOR || state.index === query.length) {
addPath(state)
} else {
state.chars += char
}
}

return state.paths
}

const getInitialState = function () {
const state = { paths: [], index: 0 }
resetPathState(state)
resetTokenState(state)
return state
}

const parseEscape = function (state, query) {
const nextChar = query[state.index + 1]

if (SPECIAL_CHARS.has(nextChar)) {
state.index += 1
state.chars += nextChar
return
}

if (state.chars.length !== 0) {
throw new Error(
`character "${ESCAPE}" must either be at the start of a token, or be followed by ${PATH_SEPARATOR_NAME} or ${TOKEN_SEPARATOR} or ${ESCAPE}`,
)
}

state.isProp = true
}

const addPath = function (state) {
if (hasNoPath(state)) {
return
}

if (!hasOnlyDots(state)) {
addToken(state)
}

// eslint-disable-next-line fp/no-mutating-methods
state.paths.push(state.path)
resetPathState(state)
}

// When the query is an empty string or when two spaces are consecutive
const hasNoPath = function (state) {
return state.firstToken && state.chars.length === 0 && state.path.length === 0
}

const resetPathState = function (state) {
state.path = []
state.firstToken = true
state.onlyDots = true
}

const addToken = function (state) {
if (handleLeadingDot(state)) {
return
}

state.onlyDots = hasOnlyDots(state)
const tokenType = getStringTokenType(state.chars, state.isProp)
const token = tokenType.normalize(tokenType.parse(state.chars))
// eslint-disable-next-line fp/no-mutating-methods
state.path.push(token)
resetTokenState(state)
}

// In principle, the root query should be an empty string.
// But we use a lone dot instead because:
// - It distinguishes it from an absence of query
// - It allows parsing it in the middle of a space-separated list of queries
// (as opposed to an empty string)
// However, we create ambiguities for queries with only dots (including a
// lone dot), where the last dot should not create an additional token.
const hasOnlyDots = function (state) {
return state.onlyDots && state.chars.length === 0
}

// We ignore leading dots, because they are used to represent the root.
// We do not require them for simplicity.
const handleLeadingDot = function (state) {
if (!state.firstToken) {
return false
}

state.firstToken = false
return state.chars.length === 0
}

const resetTokenState = function (state) {
state.isProp = false
state.chars = ''
}

0 comments on commit 960ebef

Please sign in to comment.