Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: replace lodash with remeda #1952

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module.exports = {
{
// For sub-packages using TypeScript (libraries/VSCode Exts) && TypeScript definitions (d.ts)
files: ["*.ts"],
plugins: ["@typescript-eslint", "lodash"],
plugins: ["@typescript-eslint"],
parser: "@typescript-eslint/parser",
parserOptions: {
// project: ["./tsconfig.base.json", "./tsconfig.json"],
Expand All @@ -48,7 +48,6 @@ module.exports = {
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
],
rules: {
"lodash/import-scope": ["error", "method"],
"@typescript-eslint/no-use-before-define": [
"error",
// These can be safely used before they are defined due to function hoisting in EcmaScript
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
"husky": "8.0.2",
"lerna": "6.1.0",
"lint-staged": "13.1.0",
"lodash": "4.17.21",
"mocha": "10.2.0",
"npm-run-all": "4.1.5",
"prettier": "2.8.4",
Expand All @@ -72,7 +71,6 @@
"@typescript-eslint/eslint-plugin": "5.47.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-lodash": "7.4.0",
"source-map-support": "0.5.21",
"@istanbuljs/schema": "0.1.3",
"glob": "8.0.3",
Expand Down
127 changes: 74 additions & 53 deletions packages/chevrotain/benchmark_web/parsers/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ var parserInstance
var lexerInstance
var lexResult

/**
* uncomment this alternative `parseBench` to measure startup time performance
*/
function parseBench(
text,
lexerDefinition,
Expand All @@ -12,58 +15,76 @@ function parseBench(
options,
parserConfig
) {
if (lexerInstance === undefined) {
if (customLexer !== undefined) {
lexerInstance = customLexer
} else {
var start = new Date().getTime()
lexerInstance = new chevrotain.Lexer(lexerDefinition, {
// TODO: extract lexer options to global config
positionTracking: "onlyOffset"
})
var end = new Date().getTime()
console.log("Lexer init time: " + (end - start))
}
}

if (lexResult === undefined || options.lexerOnly) {
lexResult = lexerInstance.tokenize(text)
if (lexResult.errors.length > 0) {
throw Error("Lexing errors detected")
}
}

// It is recommended to only initialize a Chevrotain Parser once
// and reset it's state instead of re-initializing it
if (parserInstance === undefined) {
var start = new Date().getTime()
parserInstance = new parser(parserConfig)
var end = new Date().getTime()
console.log("Parser init time: " + (end - start))
}

if (options.lexerOnly) {
return lexResult.tokens
} else {
// setting a new input will RESET the parser instance's state.
parserInstance.input = lexResult.tokens
var lexErrors = lexResult.errors

// only performing the lexing ONCE if we are only interested in the parsing speed
if (!options.parserOnly) {
lexResult = undefined
}

// any top level rule may be used as an entry point
var value = parserInstance[rootRule]()

if (parserInstance.errors.length > 0) {
throw Error("Parsing Errors detected")
}
return {
value: value, // this is a pure grammar, the value will always be <undefined>
lexErrors: lexErrors,
parseErrors: parserInstance.errors
}
if (lexerDefinition) {
lexerInstance = new chevrotain.Lexer(lexerDefinition, {
positionTracking: "onlyOffset"
})
}
parserInstance = new parser(parserConfig)
}

// function parseBench(
// text,
// lexerDefinition,
// customLexer,
// parser,
// rootRule,
// options,
// parserConfig
// ) {
// if (lexerInstance === undefined) {
// if (customLexer !== undefined) {
// lexerInstance = customLexer
// } else {
// var start = new Date().getTime()
// lexerInstance = new chevrotain.Lexer(lexerDefinition, {
// // TODO: extract lexer options to global config
// positionTracking: "onlyOffset"
// })
// var end = new Date().getTime()
// console.log("Lexer init time: " + (end - start))
// }
// }
// parserInstance = new parser(parserConfig)
//
// if (lexResult === undefined || options.lexerOnly) {
// lexResult = lexerInstance.tokenize(text)
// if (lexResult.errors.length > 0) {
// throw Error("Lexing errors detected")
// }
// }
//
// // It is recommended to only initialize a Chevrotain Parser once
// // and reset it's state instead of re-initializing it
// if (parserInstance === undefined) {
// var start = new Date().getTime()
// parserInstance = new parser(parserConfig)
// var end = new Date().getTime()
// console.log("Parser init time: " + (end - start))
// }
//
// if (options.lexerOnly) {
// return lexResult.tokens
// } else {
// // setting a new input will RESET the parser instance's state.
// parserInstance.input = lexResult.tokens
// var lexErrors = lexResult.errors
//
// // only performing the lexing ONCE if we are only interested in the parsing speed
// if (!options.parserOnly) {
// lexResult = undefined
// }
//
// // any top level rule may be used as an entry point
// var value = parserInstance[rootRule]()
//
// if (parserInstance.errors.length > 0) {
// throw Error("Parsing Errors detected")
// }
// return {
// value: value, // this is a pure grammar, the value will always be <undefined>
// lexErrors: lexErrors,
// parseErrors: parserInstance.errors
// }
// }
// }
4 changes: 2 additions & 2 deletions packages/chevrotain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@
"@chevrotain/types": "10.5.0",
"@chevrotain/utils": "10.5.0",
"@chevrotain/regexp-to-ast": "10.5.0",
"lodash": "4.17.21"
"remeda": "1.23.0",
"is-regexp": "2.1.0"
},
"devDependencies": {
"@types/lodash": "4.14.191",
"error-stack-parser": "2.1.4",
"esbuild": "0.16.10",
"gen-esm-wrapper": "1.1.3",
Expand Down
19 changes: 9 additions & 10 deletions packages/chevrotain/src/parse/cst/cst_visitor.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import isEmpty from "lodash/isEmpty"
import compact from "lodash/compact"
import isArray from "lodash/isArray"
import map from "lodash/map"
import forEach from "lodash/forEach"
import filter from "lodash/filter"
import keys from "lodash/keys"
import isFunction from "lodash/isFunction"
import isUndefined from "lodash/isUndefined"
import { isEmpty } from "remeda/dist/commonjs/isEmpty"
import { compact } from "@chevrotain/utils"
import { isArray } from "remeda/dist/commonjs/isArray"
import { map } from "@chevrotain/utils"
import { forEach } from "@chevrotain/utils"
import { filter } from "@chevrotain/utils"
import { keys } from "remeda/dist/commonjs/keys"
import { isFunction } from "remeda/dist/commonjs/isFunction"
import { defineNameProp } from "../../lang/lang_extensions"
import { CstNode, ICstVisitor } from "@chevrotain/types"

Expand Down Expand Up @@ -51,7 +50,7 @@ export function createBaseSemanticVisitorConstructor(
}

// enables passing optional CstNodes concisely.
if (isUndefined(cstNode)) {
if (cstNode === undefined) {
return undefined
}

Expand Down
6 changes: 3 additions & 3 deletions packages/chevrotain/src/parse/errors_public.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { hasTokenLabel, tokenLabel } from "../scan/tokens_public"
import first from "lodash/first"
import map from "lodash/map"
import reduce from "lodash/reduce"
import { first } from "remeda/dist/commonjs/first"
import { map } from "@chevrotain/utils"
import { reduce } from "@chevrotain/utils"
import { Alternation, NonTerminal, Rule, Terminal } from "@chevrotain/gast"
import { getProductionDslName } from "@chevrotain/gast"
import {
Expand Down
2 changes: 1 addition & 1 deletion packages/chevrotain/src/parse/exceptions_public.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import includes from "lodash/includes"
import { includes } from "@chevrotain/utils"
import {
IToken,
IRecognitionException,
Expand Down
93 changes: 47 additions & 46 deletions packages/chevrotain/src/parse/grammar/checks.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import first from "lodash/first"
import isEmpty from "lodash/isEmpty"
import drop from "lodash/drop"
import flatten from "lodash/flatten"
import filter from "lodash/filter"
import reject from "lodash/reject"
import difference from "lodash/difference"
import map from "lodash/map"
import forEach from "lodash/forEach"
import groupBy from "lodash/groupBy"
import reduce from "lodash/reduce"
import pickBy from "lodash/pickBy"
import values from "lodash/values"
import includes from "lodash/includes"
import flatMap from "lodash/flatMap"
import clone from "lodash/clone"
import { flatMap } from "@chevrotain/utils"
import { first } from "remeda/dist/commonjs/first"
import { isEmpty } from "remeda/dist/commonjs/isEmpty"
import { drop } from "remeda/dist/commonjs/drop"
import { flatten } from "@chevrotain/utils"
import { filter } from "@chevrotain/utils"
import { reject } from "@chevrotain/utils"
import { difference } from "@chevrotain/utils"
import { map } from "@chevrotain/utils"
import { forEach } from "@chevrotain/utils"
import { groupBy } from "remeda/dist/commonjs/groupBy"
import { reduce } from "@chevrotain/utils"
import { pickBy } from "remeda/dist/commonjs/pickBy"
import { values } from "remeda/dist/commonjs/values"
import { includes, shallowClone } from "@chevrotain/utils"
import {
IParserAmbiguousAlternativesDefinitionError,
IParserDuplicatesDefinitionError,
Expand Down Expand Up @@ -53,8 +52,8 @@ import {
IGrammarValidatorErrorMessageProvider,
IParserDefinitionError
} from "./types"
import dropRight from "lodash/dropRight"
import compact from "lodash/compact"
import { dropLast } from "remeda/dist/commonjs/dropLast"
import { compact } from "@chevrotain/utils"
import { tokenStructuredMatcher } from "../../scan/tokens"

export function validateLookahead(options: {
Expand Down Expand Up @@ -294,7 +293,7 @@ export function validateNoLeftRecursion(
// other cyclic paths are ignored, we still need this difference to avoid infinite loops...
const validNextSteps = difference(nextNonTerminals, path.concat([topRule]))
const errorsFromNextSteps = flatMap(validNextSteps, (currRefRule) => {
const newPath = clone(path)
const newPath = shallowClone(path)
newPath.push(currRefRule)
return validateNoLeftRecursion(
topRule,
Expand Down Expand Up @@ -345,7 +344,7 @@ export function getFirstNoneTerminal(definition: IProduction[]): Rule[] {
const isFirstOptional = isOptionalProd(firstProd)
const hasMore = definition.length > 1
if (isFirstOptional && hasMore) {
const rest = drop(definition)
const rest = drop(definition, 1)
return result.concat(getFirstNoneTerminal(rest))
} else {
return result
Expand All @@ -371,32 +370,34 @@ export function validateEmptyOrAlternative(
const errors = flatMap<Alternation, IParserEmptyAlternativeDefinitionError>(
ors,
(currOr) => {
const exceptLast = dropRight(currOr.definition)
return flatMap(exceptLast, (currAlternative, currAltIdx) => {
const possibleFirstInAlt = nextPossibleTokensAfter(
[currAlternative],
[],
tokenStructuredMatcher,
1
)
if (isEmpty(possibleFirstInAlt)) {
return [
{
message: errMsgProvider.buildEmptyAlternationError({
topLevelRule: topLevelRule,
alternation: currOr,
emptyChoiceIdx: currAltIdx
}),
type: ParserDefinitionErrorType.NONE_LAST_EMPTY_ALT,
ruleName: topLevelRule.name,
occurrence: currOr.idx,
alternative: currAltIdx + 1
}
]
} else {
return []
}
})
const exceptLast = dropLast(currOr.definition, 1)
return flatten(
map(exceptLast, (currAlternative, currAltIdx) => {
const possibleFirstInAlt = nextPossibleTokensAfter(
[currAlternative],
[],
tokenStructuredMatcher,
1
)
if (isEmpty(possibleFirstInAlt)) {
return [
{
message: errMsgProvider.buildEmptyAlternationError({
topLevelRule: topLevelRule,
alternation: currOr,
emptyChoiceIdx: currAltIdx
}),
type: ParserDefinitionErrorType.NONE_LAST_EMPTY_ALT,
ruleName: topLevelRule.name,
occurrence: currOr.idx,
alternative: currAltIdx + 1
}
]
} else {
return []
}
})
)
}
)

Expand Down
8 changes: 4 additions & 4 deletions packages/chevrotain/src/parse/grammar/first.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import flatten from "lodash/flatten"
import uniq from "lodash/uniq"
import map from "lodash/map"
import { flatten } from "@chevrotain/utils"
import { uniq } from "@chevrotain/utils"
import { map } from "@chevrotain/utils"
import { NonTerminal, Terminal } from "@chevrotain/gast"
import {
isBranchingProd,
Expand Down Expand Up @@ -63,7 +63,7 @@ export function firstForBranching(prod: {
return first(innerProd)
}
)
return uniq(flatten<TokenType>(allAlternativesFirsts))
return uniq(flatten(allAlternativesFirsts))
}

export function firstForTerminal(terminal: Terminal): TokenType[] {
Expand Down
6 changes: 3 additions & 3 deletions packages/chevrotain/src/parse/grammar/follow.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { RestWalker } from "./rest"
import { first } from "./first"
import forEach from "lodash/forEach"
import assign from "lodash/assign"
import { forEach } from "@chevrotain/utils"
import { IN } from "../constants"
import { Alternative, NonTerminal, Rule, Terminal } from "@chevrotain/gast"
import { IProduction, TokenType } from "@chevrotain/types"
Expand Down Expand Up @@ -50,7 +49,8 @@ export function computeAllProdsFollows(

forEach(topProductions, (topProd) => {
const currRefsFollow = new ResyncFollowsWalker(topProd).startWalking()
assign(reSyncFollows, currRefsFollow)
// `remeda`'s merge create a new object, but we prefer max performance here.
Object.assign(reSyncFollows, currRefsFollow)
})
return reSyncFollows
}
Expand Down
Loading