Skip to content

Commit

Permalink
Better readme
Browse files Browse the repository at this point in the history
  • Loading branch information
barsdeveloper committed Dec 16, 2023
1 parent da1c643 commit 4fb2ffd
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 13 deletions.
46 changes: 35 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,59 @@
# Parsernostrum

Parsernostrum is a small LL parsing combinator library for JavaScript, designed to be very simple leveraging modern JavaScript features and keeping code size to a minimum, particularly usefull in frontend contexts. It offers a set of tools to create robust and maintainable parsers with very little code.
Parsernostrum is a small non backtracking LL parsing combinator library for JavaScript, designed to be very simple leveraging modern JavaScript features and keeping code size to a minimum. It is particularly suitable in frontend contexts. It offers a set of tools to create robust and maintainable parsers with very little code.

## Getting started

```sh
npm install parsernostrum
```

Import Parsernostrum and use it to create custom parsers tailored to your specific parsing needs.
Import Parsernostrum and use it to create custom parsers tailored to your specific parsing needs. Then use the following methods to parse a astring.

```JavaScript
import P from "parsernostrum"

// Create a parser
/** @type {P<any>} */
const palindromeParser = P.alt(
P.regexp(/[a-z]/).chain(c =>
P.seq(
P.lazy(() => palindromeParser).opt(),
P.str(c)
).map(([recursion, _]) => c + recursion + c)
),
P.regexp(/([a-z])\1?/)
).opt()

// Use the parsing methods to check the text
try {
// This method throws in case it doesn't parse
palindromeParser.parse("Not a palindrome!")
} catch (e) {
console.log(e.message) // Could not parse "Not a palindrome!"
}
// This method returns an object with status (can be used as a boolean to check if success) and value keys
let result = palindromeParser.run("Also not a palindrome")
console.log(result.value) // null
console.log(palindromeParser.parse("asantalivedasadevilatnasa")) // asantalivedasadevilatnasa
```

Then you have access to the following tools:
## Documentation

### `str(string)`
### `str(value)`
Parses exact string literals.
```JavaScript
regexp(regexp, group)
P.str("A string!")
```

### `regexp(regexp)`
### `regexp(value, group)`
Parses a regular expression and possibly returns a captured group.
```JavaScript
P.regexp(/\d+/)
```

### `regexpGroups(regexp)`
Parses a regular expression returns all its captured groups exactly as returned by the `RegExp.exec()` method.
### `regexpGroups(value)`
Parses a regular expression and returns all its captured groups exactly as returned by the `RegExp.exec()` method.
```JavaScript
P.regexpGroups(/begin\s*(\w*)\s*(\w*)\s*end/)
```
Expand Down Expand Up @@ -64,9 +88,9 @@ const matcheParentheses = P.seq(
P.str(")"),
)
```
[!WARNING]
LL parsers do not generally support left recursion. It is therefore important that your recursive parsers always have an actual parser as the first element (in this case P.str("("))). Otherwise the code will result in a runtime infinite recursion exception.
In general it is always possible to rewrite a grammar to remove left recursion.
>[!WARNING]
>LL parsers do not generally support left recursion. It is therefore important that your recursive parsers always have an actual parser as the first element (in this case `P.str("("))`). Otherwise the code will result in a runtime infinite recursion exception.
>In general it is always possible to rewrite a grammar to remove left recursion.
### `.times(min, max)`
Matches a parser a specified number of times.
Expand Down
7 changes: 5 additions & 2 deletions src/Parsernostrum.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,14 @@ export default class Parsernostrum {
return result.status && result.position === input.length ? result : Reply.makeFailure(result.position)
}

/** @param {String} input */
/**
* @param {String} input
* @throws when the parser fails to match
*/
parse(input) {
const result = this.run(input)
if (!result.status) {
throw new Error("Parsing error")
throw new Error(`Could not parse "${input.length > 20 ? input.substring(0, 17) + "..." : input}"`)
}
return result.value
}
Expand Down
45 changes: 45 additions & 0 deletions tests/advanced.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,32 @@ test.afterAll(async () => {
webserver.close()
})

test("Readme code", async ({ page }) => {
// Create a parser
/** @type {P<any>} */
const palindromeParser = P.alt(
P.regexp(/[a-z]/).chain(c =>
P.seq(
P.lazy(() => palindromeParser).opt(),
P.str(c)
).map(([recursion, _]) => c + recursion + c)
),
P.regexp(/([a-z])\1?/)
).opt()

// Use the parsing methods to check the text
try {
// This method throws in case it doesn't parse
palindromeParser.parse("Not a palindrome!")
} catch (e) {
console.log(e.message) // Could not parse "Not a palindrome!"
}
// This method returns an object with status (can be used as a boolean to check if success) and value keys
let result = palindromeParser.run("Also not a palindrome")
console.log(result.value) // null
console.log(palindromeParser.parse("asantalivedasadevilatnasa")) // asantalivedasadevilatnasa
})

test("Matched parentheses", async ({ page }) => {
/** @type {P<any>} */
const matcheParentheses = P.seq(
Expand All @@ -36,6 +62,25 @@ test("Matched parentheses", async ({ page }) => {
expect(() => matcheParentheses.parse("(()")).toThrow()
})

test("Palindrome", async ({ page }) => {
const palindromeParser = P.alt(
P.regexp(/[a-z]/).chain(c =>
P.seq(
P.lazy(() => palindromeParser).opt(),
P.str(c)
).map(([recursion, _]) => c + recursion + c)
),
P.regexp(/([a-z])\1?/)
).opt()
expect(palindromeParser.parse("")).toEqual("")
expect(palindromeParser.parse("a")).toEqual("a")
expect(palindromeParser.parse("aa")).toEqual("aa")
expect(palindromeParser.parse("aba")).toEqual("aba")
expect(palindromeParser.parse("abba")).toEqual("abba")
expect(palindromeParser.parse("racecar")).toEqual("racecar")
expect(palindromeParser.parse("asantalivedasadevilatnasa")).toEqual("asantalivedasadevilatnasa")
})

test("Arithmetic", async ({ page }) => {
const expression = MathGrammar.expression
expect(expression.parse("1")).toEqual(1)
Expand Down

0 comments on commit 4fb2ffd

Please sign in to comment.