jsdoc-type-pratt-parser
Advanced tools
Comparing version 2.2.0 to 2.2.1
import { KeyValueResult } from './result/NonRootResult'; | ||
import { NameResult, NumberResult, RootResult, VariadicResult } from './result/RootResult'; | ||
import { IntermediateResult } from './result/IntermediateResult'; | ||
/** | ||
* Throws an error if the provided result is not a {@link RootResult} | ||
*/ | ||
export declare function assertRootResult(result?: IntermediateResult): RootResult; | ||
@@ -5,0 +8,0 @@ export declare function assertPlainKeyValueOrRootResult(result: IntermediateResult): KeyValueResult | RootResult; |
@@ -1,2 +0,2 @@ | ||
import { Token, TokenType } from './lexer/Token'; | ||
import { TokenType } from './lexer/Token'; | ||
import { Lexer } from './lexer/Lexer'; | ||
@@ -17,11 +17,29 @@ import { Grammar } from './grammars/Grammar'; | ||
constructor({ grammar, lexer, parent }: ParserOptions); | ||
/** | ||
* Parses a given string and throws an error if the parse ended before the end of the string. | ||
*/ | ||
parseText(text: string): RootResult; | ||
/** | ||
* Parses with the current lexer and asserts that the result is a {@link RootResult}. | ||
*/ | ||
parseType(precedence: Precedence): RootResult; | ||
/** | ||
* Tries to parse the current state with all parslets in the grammar and returns the first non null result. | ||
*/ | ||
private tryParslets; | ||
/** | ||
* The main parsing function. First it tries to parse the current state in the prefix step, and then it continues | ||
* to parse the state in the infix step. | ||
*/ | ||
parseIntermediateType(precedence: Precedence): IntermediateResult; | ||
/** | ||
* In the infix parsing step the parser continues to parse the current state with all parslets until none returns | ||
* a result. | ||
*/ | ||
parseInfixIntermediateType(result: IntermediateResult, precedence: Precedence): IntermediateResult; | ||
/** | ||
* If the given type equals the current type of the {@link Lexer} advance the lexer. Return true if the lexer was | ||
* advanced. | ||
*/ | ||
consume(types: TokenType | TokenType[]): boolean; | ||
getToken(): Token; | ||
peekToken(): Token; | ||
previousToken(): Token | undefined; | ||
getLexer(): Lexer; | ||
@@ -28,0 +46,0 @@ getParent(): Parser | undefined; |
import { NonRootResult } from './result/NonRootResult'; | ||
import { RootResult } from './result/RootResult'; | ||
/** | ||
* A node visitor function. | ||
* @param node the visited node. | ||
* @param parentNode the parent node. | ||
* @param property the property on the parent node that contains the visited node. It can be the node itself or | ||
* an array of nodes. | ||
*/ | ||
declare type NodeVisitor = (node: NonRootResult, parentNode?: NonRootResult, property?: string) => void; | ||
/** | ||
* A function to traverse an AST. It traverses it depth first. | ||
* @param node the node to start traversing at. | ||
* @param onEnter node visitor function that will be called on entering the node. This corresponds to preorder traversing. | ||
* @param onLeave node visitor function that will be called on leaving the node. This corresponds to postorder traversing. | ||
*/ | ||
export declare function traverse(node: RootResult, onEnter?: NodeVisitor, onLeave?: NodeVisitor): void; | ||
export {}; |
{ | ||
"name": "jsdoc-type-pratt-parser", | ||
"version": "2.2.0", | ||
"version": "2.2.1", | ||
"description": "", | ||
@@ -21,3 +21,4 @@ "main": "dist/index.js", | ||
"prepublishOnly": "npm run build", | ||
"semantic-release": "semantic-release" | ||
"semantic-release": "semantic-release", | ||
"benchmark": "npm run build && node benchmark/benchmark.js" | ||
}, | ||
@@ -24,0 +25,0 @@ "author": "Simon Seyock (https://github.com/simonseyock)", |
119
README.md
@@ -9,7 +9,10 @@ [![Npm Package](https://badgen.net/npm/v/jsdoc-type-pratt-parser)](https://www.npmjs.com/package/jsdoc-type-pratt-parser) | ||
This project is a parser for jsdoc types. It is heavily inspired by the existing libraries catharsis and | ||
jsdoctypeparser, but does not use PEG.js, instead it is written as a pratt parser. | ||
* https://github.com/hegemonic/catharsis | ||
* https://github.com/jsdoctypeparser/jsdoctypeparser | ||
This project is a parser for jsdoc types. It takes jsdoc type expressions like `Array<string>` and creates an abstract | ||
syntax tree (AST) out of it. It is heavily inspired by the existing libraries [catharsis](https://github.com/hegemonic/catharsis) and [jsdoctypeparser](https://github.com/jsdoctypeparser/jsdoctypeparser), but does | ||
not use [PEG.js](https://pegjs.org/), instead it is written as a pratt parser. | ||
You can find some more information about pratt parsers here: | ||
* https://en.wikipedia.org/wiki/Operator-precedence_parser#Pratt_parsing | ||
* http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ | ||
* https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html | ||
@@ -22,2 +25,3 @@ ## Table of Contents | ||
* [Transforms](#transforms) | ||
* [Traverse](#traverse) | ||
* [Tests Status](#tests-status) | ||
@@ -40,3 +44,3 @@ * [Performance](#performance) | ||
const result = parse('myType.<string>', 'closure') | ||
const result = parse('SomeType<string>', 'typescript') | ||
``` | ||
@@ -46,3 +50,4 @@ | ||
An API documentation can be found here: https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html | ||
An API documentation can be found [here](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html). | ||
It is still lacking in some points. Feel free to create issues or PRs to improve this. | ||
@@ -55,46 +60,64 @@ ## Available Grammars | ||
This library supports compatibility modes for catharsis and jsdoctypeparser. The provided transform functions attempt to | ||
transform the output to the expected output of the target library. This will not always be the same as some types are | ||
parsed differently. These modes are thought to make transition easier, but it is advised to use the native output as | ||
this will be more uniform and will contain more information. | ||
Catharsis compat mode: | ||
A common task to do on ASTs are transforms, for example a stringification. This library includes some transform and | ||
utilities to implement your own. | ||
[`stringify`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#stringify): | ||
```js | ||
import { parse, catharsisTransform } from 'jsdoc-type-pratt-parser' | ||
import { stringify } from 'jsdoc-type-pratt-parser' | ||
const result = catharsisTransform(parse('myType.<string>', 'closure')) | ||
const val = stringify({ type: 'JsdocTypeName', value: 'name'}) // -> 'name' | ||
``` | ||
Jsdoctypeparser compat mode: | ||
You can customize the stringification by using [`stringifyRules`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#stringifyRules) | ||
and [`transform`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#transform): | ||
```js | ||
import { parse, jtpTransform } from 'jsdoc-type-pratt-parser' | ||
import { stringifyRules, transform } from 'jsdoc-type-pratt-parser' | ||
const result = jtpTransform(parse('myType.<string>', 'closure')) | ||
const rules = stringifyRules() | ||
// `result` is the current node and `transform` is a function to transform child nodes. | ||
rules.NAME = (result, transform) => 'something else' | ||
const val = transform(rules, { type: 'JsdocTypeName', value: 'name'}) // -> 'something else' | ||
``` | ||
Stringify: | ||
You can also build your own transform rules by implementing the [`TransformRules<TransformResultType>`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#TransformRules) interface or you | ||
can build upon the [identity ruleset](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#identityTransformRules) like this: | ||
```js | ||
import { stringify } from 'jsdoc-type-pratt-parser' | ||
import { identityTransformRules, transform } from 'jsdoc-type-pratt-parser' | ||
const val = stringify({ type: 'JsdocTypeName', value: 'name'}) // -> 'name' | ||
const myRules = identityTransformRules() | ||
myRules.NAME = () => ({ type: 'JsdocTypeName', value: 'funky' }) | ||
const val = transform(myRules, result) | ||
``` | ||
You can customize the stringification by using `stringifyRules` and `transform`: | ||
This library also supports compatibility modes for catharsis and jsdoctypeparser. The provided transform functions attempt to | ||
transform the output to the expected output of the target library. This will not always be the same as some types are | ||
parsed differently. These modes are thought to make transition easier, but it is advised to use the native output as | ||
this will be more uniform and will contain more information. | ||
[Catharsis compat mode](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#catharsisTransform): | ||
```js | ||
import { stringifyRules, transform } from 'jsdoc-type-pratt-parser' | ||
import { parse, catharsisTransform } from 'jsdoc-type-pratt-parser' | ||
const rules = stringifyRules() | ||
const result = catharsisTransform(parse('myType.<string>', 'closure')) | ||
``` | ||
// `result` is the current node and `transform` is a function to transform child nodes. | ||
rules.NAME = (result, transform) => 'something else' | ||
[Jsdoctypeparser compat mode](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#jtpTransform): | ||
const val = transform(rules, { type: 'JsdocTypeName', value: 'name'}) // -> 'something else' | ||
```js | ||
import { parse, jtpTransform } from 'jsdoc-type-pratt-parser' | ||
const result = jtpTransform(parse('myType.<string>', 'closure')) | ||
``` | ||
You can traverse a result tree with the `traverse` function: | ||
## Traverse | ||
You can traverse an AST with the [`traverse`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#traverse) function: | ||
```js | ||
@@ -112,45 +135,33 @@ import { traverse } from 'jsdoc-type-pratt-parser' | ||
You can also build your own transform rules by implementing the `TransformRules<TransformResultType>` interface or you | ||
can build upon the identity ruleset like this: | ||
```js | ||
import { identityTransformRules, transform } from 'jsdoc-type-pratt-parser' | ||
const myRules = identityTransformRules() | ||
myRules.NAME = () => ({ type: 'JsdocTypeName', value: 'funky' }) | ||
const val = transform(myRules, result) | ||
``` | ||
## Tests Status | ||
This parser runs most tests of https://github.com/hegemonic/catharsis and | ||
https://github.com/jsdoctypeparser/jsdoctypeparser. It compares the results of the different parsing libraries. If you | ||
want to find out where the output differs, look in the tests for the comments `// This seems to be an error of ...` or | ||
the `differ` keyword which indicates that differing results are produced. | ||
https://github.com/jsdoctypeparser/jsdoctypeparser. It compares the results of the different parsing libraries. If you | ||
want to find out where the output differs, look in the tests for the comments `// This seems to be an error of ...` or | ||
the `differ` keyword which indicates that differing results are produced. | ||
## Performance | ||
A simple performance [comparision](benchmark/benchmark.js) using [Benchmark.js](https://benchmarkjs.com/) produced the following results: | ||
A simple [performance comparision](benchmark/benchmark.js) using [Benchmark.js](https://benchmarkjs.com/) produced the following results: | ||
``` | ||
Testing expression: Name | ||
catharsis x 36,338 ops/sec ±1.10% (1071 runs sampled) | ||
jsdoc-type-pratt-parser x 400,260 ops/sec ±0.87% (1070 runs sampled) | ||
jsdoctypeparser x 61,847 ops/sec ±1.18% (1071 runs sampled) | ||
catharsis x 37,816 ops/sec ±1.22% (1086 runs sampled) | ||
jsdoc-type-pratt-parser x 602,617 ops/sec ±0.16% (1090 runs sampled) | ||
jsdoctypeparser x 53,256 ops/sec ±0.73% (1081 runs sampled) | ||
The fastest was jsdoc-type-pratt-parser | ||
Testing expression: Array<number> | ||
catharsis x 7,969 ops/sec ±1.05% (1079 runs sampled) | ||
jsdoc-type-pratt-parser x 159,001 ops/sec ±0.95% (1074 runs sampled) | ||
jsdoctypeparser x 42,278 ops/sec ±1.01% (1070 runs sampled) | ||
catharsis x 10,124 ops/sec ±0.56% (1084 runs sampled) | ||
jsdoc-type-pratt-parser x 228,660 ops/sec ±0.40% (1084 runs sampled) | ||
jsdoctypeparser x 42,365 ops/sec ±0.60% (1070 runs sampled) | ||
The fastest was jsdoc-type-pratt-parser | ||
Testing expression: { keyA: Type<A | "string val" >, keyB: function(string, B): A } | ||
catharsis x 933 ops/sec ±1.15% (1070 runs sampled) | ||
jsdoc-type-pratt-parser x 29,596 ops/sec ±0.90% (1068 runs sampled) | ||
jsdoctypeparser x 16,206 ops/sec ±1.38% (1055 runs sampled) | ||
catharsis x 1,138 ops/sec ±0.66% (1087 runs sampled) | ||
jsdoc-type-pratt-parser x 46,535 ops/sec ±0.47% (1090 runs sampled) | ||
jsdoctypeparser x 18,291 ops/sec ±0.71% (1084 runs sampled) | ||
The fastest was jsdoc-type-pratt-parser | ||
``` | ||
the test uses catharsis without cache, as this is just a simple lookup table that could easily be implemented for any parser. | ||
The benchmark test uses catharsis without cache. | ||
@@ -160,2 +171,2 @@ ## Development | ||
If you want to contribute see the [Development Guide](DEVELOPMENT.md) to get some pointers. Feel free to create issues if | ||
there is missing information. | ||
there is information missing. |
@@ -6,2 +6,5 @@ import { KeyValueResult } from './result/NonRootResult' | ||
/** | ||
* Throws an error if the provided result is not a {@link RootResult} | ||
*/ | ||
export function assertRootResult (result?: IntermediateResult): RootResult { | ||
@@ -8,0 +11,0 @@ if (result === undefined) { |
import { EarlyEndOfParseError, NoParsletFoundError } from './errors' | ||
import { Token, TokenType } from './lexer/Token' | ||
import { TokenType } from './lexer/Token' | ||
import { Lexer } from './lexer/Lexer' | ||
@@ -30,7 +30,10 @@ import { Grammar } from './grammars/Grammar' | ||
/** | ||
* Parses a given string and throws an error if the parse ended before the end of the string. | ||
*/ | ||
parseText (text: string): RootResult { | ||
this.lexer.lex(text) | ||
const result = this.parseType(Precedence.ALL) | ||
if (this.getToken().type !== 'EOF') { | ||
throw new EarlyEndOfParseError(this.getToken()) | ||
if (this.lexer.token().type !== 'EOF') { | ||
throw new EarlyEndOfParseError(this.lexer.token()) | ||
} | ||
@@ -40,2 +43,5 @@ return result | ||
/** | ||
* Parses with the current lexer and asserts that the result is a {@link RootResult}. | ||
*/ | ||
public parseType (precedence: Precedence): RootResult { | ||
@@ -45,2 +51,5 @@ return assertRootResult(this.parseIntermediateType(precedence)) | ||
/** | ||
* Tries to parse the current state with all parslets in the grammar and returns the first non null result. | ||
*/ | ||
private tryParslets (precedence: Precedence, left: IntermediateResult | null): IntermediateResult | null { | ||
@@ -56,2 +65,6 @@ for (const parslet of this.grammar) { | ||
/** | ||
* The main parsing function. First it tries to parse the current state in the prefix step, and then it continues | ||
* to parse the state in the infix step. | ||
*/ | ||
public parseIntermediateType (precedence: Precedence): IntermediateResult { | ||
@@ -61,3 +74,3 @@ const result = this.tryParslets(precedence, null) | ||
if (result === null) { | ||
throw new NoParsletFoundError(this.getToken()) | ||
throw new NoParsletFoundError(this.lexer.token()) | ||
} | ||
@@ -68,2 +81,6 @@ | ||
/** | ||
* In the infix parsing step the parser continues to parse the current state with all parslets until none returns | ||
* a result. | ||
*/ | ||
public parseInfixIntermediateType (result: IntermediateResult, precedence: Precedence): IntermediateResult { | ||
@@ -80,2 +97,6 @@ let newResult = this.tryParslets(precedence, result) | ||
/** | ||
* If the given type equals the current type of the {@link Lexer} advance the lexer. Return true if the lexer was | ||
* advanced. | ||
*/ | ||
public consume (types: TokenType|TokenType[]): boolean { | ||
@@ -92,14 +113,2 @@ if (!Array.isArray(types)) { | ||
public getToken (): Token { | ||
return this.lexer.token() | ||
} | ||
public peekToken (): Token { | ||
return this.lexer.peek() | ||
} | ||
public previousToken (): Token | undefined { | ||
return this.lexer.last() | ||
} | ||
getLexer (): Lexer { | ||
@@ -106,0 +115,0 @@ return this.lexer |
@@ -41,3 +41,3 @@ import { composeParslet, ParsletFunction } from './Parslet' | ||
const hasParenthesis = parser.getToken().type === '(' | ||
const hasParenthesis = parser.getLexer().token().type === '(' | ||
@@ -44,0 +44,0 @@ if (!hasParenthesis) { |
@@ -94,4 +94,5 @@ import { ParsletFunction } from './Parslet' | ||
if (brackets && !parser.consume(']')) { | ||
throw new Error(`Unterminated square brackets. Next token is '${parser.getToken().type}' ` + | ||
`with text '${parser.getToken().text}'`) | ||
const token = parser.getLexer().token() | ||
throw new Error(`Unterminated square brackets. Next token is '${token.type}' ` + | ||
`with text '${token.text}'`) | ||
} | ||
@@ -98,0 +99,0 @@ |
@@ -7,9 +7,9 @@ import { composeParslet } from './Parslet' | ||
parsePrefix: parser => { | ||
const token = parser.getToken() | ||
const value = parseFloat(parser.getLexer().token().text) | ||
parser.consume('Number') | ||
return { | ||
type: 'JsdocTypeNumber', | ||
value: parseInt(token.text, 10) | ||
value | ||
} | ||
} | ||
}) |
@@ -12,3 +12,3 @@ import { composeParslet } from './Parslet' | ||
if (left.type !== 'JsdocTypeName') { | ||
throw new UnexpectedTypeError(left, 'Predicate always have to have names on the left side.') | ||
throw new UnexpectedTypeError(left, 'A typescript predicate always has to have a name on the left side.') | ||
} | ||
@@ -15,0 +15,0 @@ |
@@ -34,3 +34,3 @@ import { composeParslet, ParsletFunction } from './Parslet' | ||
let token = parser.getToken() | ||
let token = parser.getLexer().token() | ||
if (parser.consume('StringValue')) { | ||
@@ -50,3 +50,3 @@ result = { | ||
value += token.text | ||
token = parser.getToken() | ||
token = parser.getLexer().token() | ||
} | ||
@@ -53,0 +53,0 @@ result = { |
@@ -33,4 +33,4 @@ import { composeParslet } from './Parslet' | ||
throw new Error('Unacceptable token: ' + parser.getToken().text) | ||
throw new Error('Unacceptable token: ' + parser.getLexer().token().text) | ||
} | ||
}) |
@@ -7,9 +7,9 @@ import { composeParslet } from './Parslet' | ||
parsePrefix: parser => { | ||
const token = parser.getToken() | ||
const text = parser.getLexer().token().text | ||
parser.consume('StringValue') | ||
return { | ||
type: 'JsdocTypeStringValue', | ||
value: token.text.slice(1, -1), | ||
value: text.slice(1, -1), | ||
meta: { | ||
quote: token.text[0] === '\'' ? 'single' : 'double' | ||
quote: text[0] === '\'' ? 'single' : 'double' | ||
} | ||
@@ -16,0 +16,0 @@ } |
@@ -5,2 +5,9 @@ import { NonRootResult } from './result/NonRootResult' | ||
/** | ||
* A node visitor function. | ||
* @param node the visited node. | ||
* @param parentNode the parent node. | ||
* @param property the property on the parent node that contains the visited node. It can be the node itself or | ||
* an array of nodes. | ||
*/ | ||
type NodeVisitor = (node: NonRootResult, parentNode?: NonRootResult, property?: string) => void | ||
@@ -31,4 +38,10 @@ | ||
/** | ||
* A function to traverse an AST. It traverses it depth first. | ||
* @param node the node to start traversing at. | ||
* @param onEnter node visitor function that will be called on entering the node. This corresponds to preorder traversing. | ||
* @param onLeave node visitor function that will be called on leaving the node. This corresponds to postorder traversing. | ||
*/ | ||
export function traverse (node: RootResult, onEnter?: NodeVisitor, onLeave?: NodeVisitor): void { | ||
_traverse(node, undefined, undefined, onEnter, onLeave) | ||
} |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
225072
6285
166