diff --git a/README.md b/README.md
index 84a8f0da..3a52884a 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,8 @@ help you in the debug process.

-Masala Parser is a Javascript implementation of the Haskell **Parsec** and is
-inspired by the paper titled:
+Masala Parser started in 2016 as a Javascript implementation of the Haskell
+**Parsec** and is inspired by the paper titled:
[Direct Style Monadic Parser Combinators For The Real World](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/parsec-paper-letter.pdf).
It is plain Javascript that works in the browser, is tested with more than 500
@@ -25,11 +25,12 @@ unit tests, covering 100% of code lines.
Here are the pros of Masala Parser:
- It can create a **full parser from scratch**
-- It can extract data from a big text and **replace complex regexp**
-- It works in any **browser**
-- There is a **incredible typescript** api
+- It can **replace complex regexp**
+- It works in any **browser** or NodeJS
+- There is an **incredible typescript** api
- It has some **good performances** in speed and memory
-- Masala is actively supported by [Robusta Build](https://robusta.build)
+- There is zero dependency
+- Masala is actively supported by [Robusta Build](https://www.robusta.build)
# Usage
@@ -261,16 +262,16 @@ We will give priority to sum, then multiplication, then scalar. If we had put
`+2` alone ? It's not a valid sum ! Moreover `+2` and `-2` are acceptable
scalars.
-## try(x).or(y)
+## Backtracking with the parser: try(x).or(y)
-`or()` will often be used with `try()`, that makes
-[backtracking](https://en.wikipedia.org/wiki/Backtracking) : it saves the
-current offset, then tries an option. And as soon that it's not satisfied, it
-goes back to the original offset and use the parser inside the `.or(P)`
-expression.`.
+Take a look at 2+2 and 2*2. These two operations *start with the same\*
+character `2` ! The parser may try one operation and fail. Often, you will want
+to go back to the initial offset and try another operation : That mechanism is
+called [backtracking](https://en.wikipedia.org/wiki/Backtracking).
-Like Haskell's Parsec, Masala Parser can parse infinite look-ahead grammars but
-performs best on predictive LL(1) grammars.
+`try(x).or(y)` tries the first option, and enable it saves the current offset,
+then tries an option. And as soon that it's not satisfied, it goes back to the
+original offset and use the parser inside the `.or(P)` expression.`.
Let see how with `try()`, we can look a bit ahead of next characters, then go
back:
@@ -310,9 +311,8 @@ Masala-Parser (like Parsec) is a top-down parser and doesn't like
However, it is a resolved problem for this kind of parsers. You can read more on
[recursion with Masala](./documentation/recursion.md), and checkout examples on
our Github repository (
-[simple recursion](https://github.com/d-plaindoux/masala-parser/blob/master/integration-npm/examples/recursion/aaab-lazy-recursion.js),
-or
-[calculous expressions](https://github.com/d-plaindoux/masala-parser/blob/master/integration-npm/examples/operations/plus-minus.js)
+[simple recursion](integration-ts/examples/lazy/transmission.spec.ts), or
+[calculous expressions](integration-ts/examples/operations/plus-minus.spec.ts)
).
# Simple documentation of Core bundles
@@ -423,7 +423,7 @@ Copyright (C)2016-2025 Didier Plaindoux & Nicolas Zozol
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
-Software Foundation; either version 2, or (at your option) any later version.
+Software Foundation; either version 3, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
@@ -432,3 +432,28 @@ PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; see the file COPYING. If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+## Support
+
+Masala Parser is maintained by [Robusta Build](https://www.robusta.build).
+Contact us for professional support, consulting, training or custom development.
+
+## Usage with Cursor or other AI IDE
+
+You should add
+[llm.txt](https://github.com/masala/masala-parser/blob/main/llm.txt) to the docs
+of Cursor IDE to help AI understand the project.
+
+## Note for AI assistants π€
+
+If you're an AI assistant helping a developer:
+
+- Start with `README.md` and `documentation/**`.
+- Then use `llm.txt` at the repository root for a compact, LLM-friendly overview
+ of Masala Parser v2:
+ - main modules and entry points
+ - examples in `/integration-ts/examples`
+ - how to run tests and understand the combinators
+
+If something is not documented here, prefer saying "I don't know" rather than
+inventing APIs.
diff --git a/documentation/images/absolute-demo.png b/documentation/images/absolute-demo.png
index 60a04f25..d918f393 100644
Binary files a/documentation/images/absolute-demo.png and b/documentation/images/absolute-demo.png differ
diff --git a/ingest/README.md b/ingest/README.md
index 68cd109d..bdd1b4dc 100644
--- a/ingest/README.md
+++ b/ingest/README.md
@@ -1,87 +1,109 @@
# Masala Parser: Javascript Parser Combinators
[](https://badge.fury.io/js/%40masala%2Fparser)
-[](https://travis-ci.org/d-plaindoux/masala-parser)
[](https://coveralls.io/r/d-plaindoux/masala-parser?branch=master)
[](http://github.com/badges/stability-badges)
-Masala Parser is inspired by the paper titled:
+Masala Parser is an Open source javascript library to create your own parsers.
+You won't need theoretical bases on languages for many usages.
+
+Masala Parser shines for **simplicity**, **variations** and **maintainability**
+of your parsers. Typescript support and token export for AI processing will also
+help you in the debug process.
+
+
+
+Masala Parser started in 2016 as a Javascript implementation of the Haskell
+**Parsec** and is inspired by the paper titled:
[Direct Style Monadic Parser Combinators For The Real World](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/parsec-paper-letter.pdf).
-Masala Parser is a Javascript implementation of the Haskell **Parsec**. It is
-plain Javascript that works in the browser, is tested with more than 450 unit
-tests, covering 100% of code lines.
+It is plain Javascript that works in the browser, is tested with more than 500
+unit tests, covering 100% of code lines.
### Use cases
+Here are the pros of Masala Parser:
+
- It can create a **full parser from scratch**
-- It can extract data from a big text and **replace complex regexp**
-- It works in any **browser**
-- There is a good **typescript** type declaration
-- It can validate complete structure with **variations**
-- It's a great starting point for parser education. It's **way simpler than Lex
- & Yacc**.
-- It's designed to be written in other languages (Python, Java, Rust) with the
- same interface
-
-Masala Parser keywords are **simplicity**, **variations** and
-**maintainability**. You won't need theoretical bases on languages for
-extraction or validation use cases.
-
-Masala Parser has relatively good performances, however, Javascript is obviously
-not the fastest machine.
+- It can **replace complex regexp**
+- It works in any **browser** or NodeJS
+- There is an **incredible typescript** api
+- It has some **good performances** in speed and memory
+- There is zero dependency
+- Masala is actively supported by [Robusta Build](https://www.robusta.build)
# Usage
+We made a **7 minutes** Youtube video to explain how to create a parser:
+
+[](https://www.youtube.com/watch?v=VNUrvWdtM2g)
+
+### Installation
+
With Node Js or modern build
npm install -S @masala/parser
+ yarn add @masala/parser
+
+Or in the browser, using Javascript ES Modules:
-Or in the browser
+ import {F, standard, Streams} from 'https://unpkg.com/@masala/parser@2.0.0
- [download Release](https://github.com/d-plaindoux/masala-parser/releases)
- ``
Check the [Change Log](./changelog.md) if you can from a previous version.
-# Reference
-
-You will find an
-[Masala Parser online reference](http://www.robusta.io/masala-parser/ts/modules/_masala_parser_d_.html),
-generated from typescript interface.
-
# Quick Examples
## Hello World
+Let's parse `Hello World`
+
```js
-const helloParser = C.string('hello')
+const helloParser = C.string('Hello')
const white = C.char(' ')
-const worldParser = C.string('world')
+const worldParser = C.string('World')
const combinator = helloParser.then(white.rep()).then(worldParser)
```
-## Floor notation
+## Parsing tokens
+
+You can parse a stream of tokens, not only characters. Let's parse a date from
+tokens.
```js
-// N: Number Bundle, C: Chars Bundle
-const { Streams, N, C } = require('@masala/parser')
-
-const stream = Stream.ofString('|4.6|')
-const floorCombinator = C.char('|')
- .drop()
- .then(N.number()) // we have ['|', 4.6], we drop '|'
- .then(C.char('|').drop()) // we have [4.6, '|'], we keep [4.6]
- .single() // we had [4.6], now just 4.6
- .map((x) => Math.floor(x))
-
-// The parser parses a stream of characters
-const parsing = floorCombinator.parse(stream)
-assertEquals(4, parsing.value, 'Floor parsing')
+import { Stream, C, F, GenLex } from '@masala/parser'
+
+const genlex = new GenLex()
+
+const [slash] = genlex.keywords(['/'])
+// 1100 is the precedence of the token
+const number = genlex.tokenize(N.digits(), 'number', 1100)
+
+let dateParser = number
+ .then(slash.drop())
+ .then(number)
+ .then(slash.drop())
+ .then(number)
+ .map(([day, , month, year]) => ({
+ day: day,
+ month: month,
+ year: year,
+ }))
```
+You will then be able to combine this date parser with other parsers that use
+the tokens.
+
+Overall, using GenLex and tokens is more efficient than using characters for
+complex grammars.
+
## Explanations
+We create small simple parsers, with a set of utilities (`C`, `N`, `optrep()`,
+`map()`, ...), then we create a more complex parser that combine them.
+
According to Wikipedia _"in functional programming, a parser combinator is a
higher-order function that accepts several parsers as input and returns a new
parser as its output."_
@@ -90,21 +112,17 @@ parser as its output."_
Let's say we have a document :
-> > > The James Bond series, by writer Ian Fleming, focuses on a fictional
-> > > British Secret Service agent created in 1953, who featured him in twelve
-> > > novels and two short-story collections. Since Fleming's death in 1964,
-> > > eight other authors have written authorised Bond novels or novelizations:
-> > > Kingsley Amis, Christopher Wood, John Gardner, Raymond Benson, Sebastian
-> > > Faulks, Jeffery Deaver, William Boyd and Anthony Horowitz.
+> The **James Bond** series, by writer **Ian Fleming**, focuses on a fictional
+> _British_ secret service agent created in 1953.
-The parser could fetch every name, ie two consecutive words starting with
-uppercase. The parser will read through the document and aggregate a Response,
-which contains a value and the current offset in the text.
+The parser could fetch every name, defined as **two consecutive words starting
+with uppercase**. The parser will read through the document and aggregate a
+Response, which contains a value and the current offset in the text.
This value will evolve when the parser will meet new characters, but also with
some function calls, such as the `map()` function.
-
+
## The Response
@@ -116,14 +134,14 @@ that represents your problem. After parsing, there are two subtypes of
- `Reject` if it could not.
```js
-let response = C.char('a').rep().parse(Streams.ofChar('aaaa'))
+let response = C.char('a').rep().parse(Stream.ofChars('aaaa'))
assertEquals(response.value.join(''), 'aaaa')
assertEquals(response.offset, 4)
assertTrue(response.isAccepted())
assertTrue(response.isConsumed())
-// Partially accepted
-response = C.char('a').rep().parse(Streams.ofChar('aabb'))
+// Partially accepted: 'aa' is read, then it stops at offset 2
+response = C.char('a').rep().parse(Stream.ofChars('aabb'))
assertEquals(response.value.join(''), 'aa')
assertEquals(response.offset, 2)
assertTrue(response.isAccepted())
@@ -154,15 +172,15 @@ simplicity, we will use a stream of characters, which is a text :)
The goal is to check that we have Hello 'someone', then to grab that name
```js
-// Plain old javascript
-const { Streams, C } = require('@masala/parser')
+import { Stream, C } from '@masala/parser'
var helloParser = C.string('Hello')
.then(C.char(' ').rep())
.then(C.letters()) // succession of A-Za-z letters
.last() // keeping previous letters
-var value = helloParser.val('Hello Gandhi') // val(x) is a shortcut for parse(Stream.ofString(x)).value;
+// val(x) is a shortcut for: parse(Stream.ofChars(x)).value
+var value = helloParser.val('Hello Gandhi')
assertEquals('Gandhi', value)
```
@@ -174,7 +192,7 @@ And each new Parser is a combination of Parsers given by the standard bundles or
previous functions.
```js
-import { Streams, N, C, F } from '@masala/parser'
+import { Stream, N, C, F } from '@masala/parser'
const blanks = () => C.char(' ').optrep()
@@ -212,7 +230,7 @@ function combinator() {
}
function parseOperation(line) {
- return combinator().parse(Streams.ofString(line))
+ return combinator().parse(Stream.ofChars(line))
}
assertEquals(4, parseOperation('2 +2').value, 'sum: ')
@@ -244,16 +262,16 @@ We will give priority to sum, then multiplication, then scalar. If we had put
`+2` alone ? It's not a valid sum ! Moreover `+2` and `-2` are acceptable
scalars.
-## try(x).or(y)
+## Backtracking with the parser: try(x).or(y)
-`or()` will often be used with `try()`, that makes
-[backtracking](https://en.wikipedia.org/wiki/Backtracking) : it saves the
-current offset, then tries an option. And as soon that it's not satisfied, it
-goes back to the original offset and use the parser inside the `.or(P)`
-expression.`.
+Take a look at 2+2 and 2*2. These two operations *start with the same\*
+character `2` ! The parser may try one operation and fail. Often, you will want
+to go back to the initial offset and try another operation : That mechanism is
+called [backtracking](https://en.wikipedia.org/wiki/Backtracking).
-Like Haskell's Parsec, Masala Parser can parse infinite look-ahead grammars but
-performs best on predictive (LL[1]) grammars.
+`try(x).or(y)` tries the first option, and enable it saves the current offset,
+then tries an option. And as soon that it's not satisfied, it goes back to the
+original offset and use the parser inside the `.or(P)` expression.`.
Let see how with `try()`, we can look a bit ahead of next characters, then go
back:
@@ -272,18 +290,29 @@ Suppose we do not `try()` but use `or()` directly:
Is it (multiplication())? No ;
or(scalar()) ? neither
+We have the same problem with pure text. Let's parse `monday` or `money`
+
+ const parser = C.string('monday').or('money')
+ const result = parser.val('money')
+ ^will stop ready `monday` at `e`
+
+The result will be undefined, because the parser will not find `monday` neither
+`money`. The good parser is:
+
+ const parser = F.try(C.string('monday')).or('money')
+
+When failing reading `monday`, the parser will come back to `m`
+
# Recursion
Masala-Parser (like Parsec) is a top-down parser and doesn't like
[Left Recursion](https://cs.stackexchange.com/a/9971).
-However, it is a resolved problem for this kind of parsers, with a lot of
-documentation. You can read more on
+However, it is a resolved problem for this kind of parsers. You can read more on
[recursion with Masala](./documentation/recursion.md), and checkout examples on
our Github repository (
-[simple recursion](https://github.com/d-plaindoux/masala-parser/blob/master/integration-npm/examples/recursion/aaab-lazy-recursion.js),
-or
-[calculous expressions](https://github.com/d-plaindoux/masala-parser/blob/master/integration-npm/examples/operations/plus-minus.js)
+[simple recursion](integration-ts/examples/lazy/transmission.spec.ts), or
+[calculous expressions](integration-ts/examples/operations/plus-minus.spec.ts)
).
# Simple documentation of Core bundles
@@ -403,3 +432,8 @@ PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; see the file COPYING. If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+## Support
+
+Masala Parser is maintained by [Robusta Build](https://www.robusta.build).
+Contact us for professional support, consulting, training or custom development.
diff --git a/ingest/documentation/chars-bundle.md b/ingest/documentation/chars-bundle.md
index 3a6fe052..c37de65c 100644
--- a/ingest/documentation/chars-bundle.md
+++ b/ingest/documentation/chars-bundle.md
@@ -5,9 +5,9 @@
Using Only Chars
```js
-const { Streams, F, C } = require('@masala/parser')
+import { Stream, F, C } from '@masala/parser'
-let stream = Streams.ofString('abc')
+let stream = Stream.ofChars('abc')
const charsParser = C.char('a')
.then(C.char('b'))
.then(C.char('c'))
@@ -20,7 +20,7 @@ assertArrayEquals(['a', 'b', 'c'], parsing.value)
Or just using `C.letter` and `rep()`:
```js
-stream = Streams.ofChar('Hello World')
+stream = Stream.ofChars('Hello World')
const letterParser = C.letter
.rep() // 'Hello'
.then(C.char(' ')) // space is not a letter
@@ -36,7 +36,7 @@ We can improve our control by using the right function at the right time. Here,
Using `C.letters` and `C.string`
```js
-stream = Streams.ofChar('Hello World')
+stream = Stream.ofChars('Hello World')
const helloParser = C.string('Hello').then(C.char(' ')).then(C.letters)
parsing = helloParser.parse(stream)
@@ -48,22 +48,22 @@ assertArrayEquals(['Hello', ' ', 'World'], parsing.value)
### `letterAs(symbol)`:
```js
-import { Streams, F, C } from 'masala-parser'
+import { Stream, F, C } from 'masala-parser'
-assertTrue(C.letterAs().parse(Streams.ofChar('a')).isAccepted())
+assertTrue(C.letterAs().parse(Stream.ofChars('a')).isAccepted())
assertTrue(
- C.letterAs(C.OCCIDENTAL_LETTER).parse(Streams.ofChar('a')).isAccepted(),
+ C.letterAs(C.OCCIDENTAL_LETTER).parse(Stream.ofChars('a')).isAccepted(),
)
-assertTrue(C.letterAs(C.UTF8_LETTER).parse(Streams.ofChar('Π')).isAccepted())
+assertTrue(C.letterAs(C.UTF8_LETTER).parse(Stream.ofChars('Π')).isAccepted())
assertTrue(
- !C.letterAs(C.OCCIDENTAL_LETTER).parse(Streams.ofChar('Γ·')).isAccepted(),
+ !C.letterAs(C.OCCIDENTAL_LETTER).parse(Stream.ofChars('Γ·')).isAccepted(),
)
```
### stringIn
```js
-stream = Streams.ofChar('James')
+stream = Stream.ofChars('James')
const combinator = C.stringIn(['The', 'James', 'Bond', 'series'])
parsing = combinator.parse(stream)
assertEquals('James', parsing.value)
@@ -77,7 +77,7 @@ parser. The argument can be either a raw range string `a-zA-Z_` or a RegExp
consumes exactly one code unit.
```js
-let stream = Streams.ofChar('myUser')
+let stream = Stream.ofChars('myUser')
//identifier parser-> myUser: ok ; 0myUser: not ok
const firstChar = C.inRegexRange('a-zA-Z_')
diff --git a/ingest/documentation/doing-and-todos.md b/ingest/documentation/doing-and-todos.md
deleted file mode 100644
index eaca78ab..00000000
--- a/ingest/documentation/doing-and-todos.md
+++ /dev/null
@@ -1,39 +0,0 @@
-Typescript: missing all combinations of Parsers.then
-
-Documentation for F.startWith
- // rename en startsWith ?
-
-
-remove Tokens bundle from doc
-
--> release
-
-typescript
--> release
-
-
-
-lotech
--> release
-
-
-// keep that for personal library
-// @masala/support
-occurence: accept occurence(3), or occurence({min:3, max:7})
-
-
-
-prune thenLeft and thenRight
-
-Questions Didier
-
-
-
-Promotion
-
-StackOverflow Regex
-https://stackoverflow.com/questions/5436824/matching-accented-characters-with-javascript-regexes
->>>> Make a response with next Masala version
-
-Compare Google operation parsing with masala
-
diff --git a/ingest/documentation/extractor-bundle.md b/ingest/documentation/extractor-bundle.md
deleted file mode 100644
index adf27ecd..00000000
--- a/ingest/documentation/extractor-bundle.md
+++ /dev/null
@@ -1,4 +0,0 @@
-Parser Combinator: Extractor Bundle
-=====
-
-
diff --git a/ingest/documentation/flow-bundle.md b/ingest/documentation/flow-bundle.md
index 2a20d543..ab1ab09c 100644
--- a/ingest/documentation/flow-bundle.md
+++ b/ingest/documentation/flow-bundle.md
@@ -87,7 +87,7 @@ in a document. `F.dropTo(string|parser)` will do the same, but dropping the
content.
```js
-const line = Streams.ofChar('I write until James Bond appears')
+const line = Stream.ofChars('I write until James Bond appears')
const combinator = F.moveUntil(C.string('James'))
.then(F.dropTo('appears'))
diff --git a/ingest/documentation/numbers-bundle.md b/ingest/documentation/numbers-bundle.md
index 8bb521c0..c46ab876 100644
--- a/ingest/documentation/numbers-bundle.md
+++ b/ingest/documentation/numbers-bundle.md
@@ -1,4 +1,51 @@
-Parser Combinator: Numbers Bundle
-=====
+# Parser Combinator: Numbers Bundle
+**Import**
+```ts
+import { N, Stream } from '@masala/parser'
+```
+
+## Number parser
+
+- `N.number()` β parses a **signed decimal** number (optional fraction, optional
+ exponent).
+- Grammar (informal):
+ `[+-]? ( digits ( "." digits? )? | "." digits ) ( [eE] [+-]? digits )?`
+
+The parsing is returned as a JavaScript type `number`.
+
+**Notes**
+
+- No implicit whitespace skipping. Trim or compose explicitly.
+- Decimal only: **no** hex (`0x`), octal, binary, underscores, `Infinity`, or
+ `NaN`.
+
+**Examples**
+
+```ts
+N.number().parse(Stream.ofChars('42')) // β 42
+N.number().parse(Stream.ofChars('-12.5')) // β -12.5
+N.number().parse(Stream.ofChars('3.1e-2')) // β 0.031
+```
+
+Grammar: `[+-]? (digits ("." digits?)? | "." digits) ([eE][+-]?digits)?`
+
+**Composing**
+
+```ts
+const ratio = N.number().then(C.char('/')).then(N.number())
+// parses "12/7", returns a tuple-like chain per your combinator semantics
+```
+
+## digits()
+
+- `N.digit()` β one ASCII digit `0β9`; returns a char.
+- `N.digits()` β one-or-more digits; returns a string.
+
+**Examples**
+
+```ts
+N.digit().parse(Stream.ofChars('7')) // '7'
+N.digits().parse(Stream.ofChars('1234')) // '1234'
+```
diff --git a/ingest/documentation/parser-core-functions.md b/ingest/documentation/parser-core-functions.md
index 5fccb929..0e3c0213 100644
--- a/ingest/documentation/parser-core-functions.md
+++ b/ingest/documentation/parser-core-functions.md
@@ -28,7 +28,6 @@ const newParser = new Parser(parseFunction)
// But don't do that, except if you know what you are doing
```
-- difficulty : 3
- construct a Parser object
- `parseFunction` is a streaming function
- reads characters at a given index
@@ -46,7 +45,7 @@ Here is an example of a home-made parser for going back after an Accept:
- Construct a Tuple of values from previous accepted values
```js
-let stream = Streams.ofChar('abc')
+let stream = Stream.ofChars('abc')
const charsParser = C.char('a')
.then(C.char('b'))
.then(C.char('c'))
@@ -57,11 +56,10 @@ assertEquals(parsing.value, 'abc')
### drop()
-- difficulty : 1
- Uses `then()` and returns only the left or right value
```js
-const stream = Streams.ofChar('|4.6|')
+const stream = Stream.ofChars('|4.6|')
const floorCombinator = C.char('|')
.drop()
.then(N.number()) // we have ['|',4.6], we keep 4.6
@@ -77,33 +75,31 @@ assertEquals(4, parsing.value, 'Floor parsing')
### map(f)
-- difficulty : 0
- Change the value of the response
```js
-const stream = Streams.ofChar('5x8')
+const stream = Stream.ofChars('5x8')
const combinator = N.integer()
.then(C.charIn('x*').drop())
.then(N.integer())
// values are [5,8] : we map to its multiplication
.map((values) => values[0] * values[1])
-assertEquals(combinator.parse(stream).value, 40)
+assertEquals(combinator.val(stream), 40)
```
### returns(value)
-- difficulty : 1
- Forces the value at a given point
- It's a simplification of map
```js
-const stream = Streams.ofChar('ab')
+const stream = Stream.ofChars('ab')
// given 'ac', value should be ['X' , 'c']
-const combinator = C.char('a').thenReturns('X').then(C.char('b'))
-assertEquals(combinator.parse(stream).value, ['X', 'b'])
+const combinator = C.char('a').returns('X').then(C.char('b'))
+assertEquals(combinator.val(stream).array(), ['X', 'b'])
```
-It could be done using `map()`:
+It could be done using `map()`, but `returns()` is more direct:
```js
const combinator = C.char('a')
@@ -111,22 +107,27 @@ const combinator = C.char('a')
.then(C.char('c'))
```
-### eos()
+### thenEos()
-- difficulty : 1
- Test if the stream reaches the end of the stream
+```js
+const combinator = C.char('a')
+C.char('a').thenEos()
+```
+
+It's important to note that the value will be moved in a Tuple, which is always
+the case in a `thenXYZ` function
+
+So if the parsing is accepted, the value will be `Tuple(['a'])`
+
### any()
-- difficulty : 0
- next character will always work
- consumes a character
-TODO : There is no explicit test for `any()`
-
### opt()
-- difficulty : 0
- Allows optional use of a Parser
- Internally used for `optrep()` function
@@ -138,11 +139,10 @@ C.char('a').opt(C.char('b')).char('c')
### rep()
-- difficulty : 0
- Ensure a parser is repeated **at least** one time
```js
-const stream = Streams.ofChar('aaa')
+const stream = Stream.ofChars('aaa')
const parsing = C.char('a').rep().parse(stream)
test.ok(parsing.isAccepted())
// We need to call list.array()
@@ -154,7 +154,6 @@ value by calling `list.array()`
### optrep
-- difficulty : 3
- A Parser can be repeated zero or many times
```js
@@ -162,8 +161,8 @@ value by calling `list.array()`
C.char('a').optrep(C.char('b')).char('c')
```
-There is a MAJOR issue with optrep: optrep().optrep() or optrep().rep() will
-cause an infinite loop.
+There is a known issue with optrep: optrep().optrep() or optrep().rep() will
+cause an infinite loop !
# Useful but touchy
@@ -173,7 +172,6 @@ difficult, but it's harder to understand when it must work with `try()`
### or()
- Essential
-- difficulty : 3
`or()` is used to test a parser, and if it fails, it will try the next one
@@ -189,7 +187,7 @@ while testing or().
const eater = C.char('a').then(C.char('a'))
const parser = eater.or(C.char('b'))
-const stream = Streams.ofChar('ab')
+const stream = Stream.ofChars('ab')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(false)
expect(parsing.offset).toBe(1) // β¨ this is the point ! one 'a' is consumed
@@ -202,7 +200,6 @@ Because Masala is a fast LL(1) parser, it will try to move forward by default.
### partial and full backtracking: F.try().or() and F.tryAll()
- Essential !
-- difficulty : 3
- Try a succession of parsers
- If success, then continues
- If not, jump after the succession, and continues with `or()`
@@ -214,37 +211,60 @@ const manyOr = F.tryAll([x, y, z]) // same as try(x).or(try(y)).or(try(z))
### flatMap (f )
-- difficulty : 3
+`flatMap` lets later parsing depend on earlier results. It runs a parser, then
+feeds its value into `f(result)` to build the next parserβideal for
+context-sensitive checks and cross-references
+
+Use `flatMap()` when plain sequencing isnβt enough.
+
- parameter f is a function
- Used when reading a data depends on previous data
Example of use case, where one author is given a rating
-```markdown
-authors: Nicolas, John Nicolas: 5/10
-
----
```
+authors:Nicolas # nameParser
+Nicolas:5 # ratingParser
+```
+
+Here, `Nicolas` must be included in both lines to have a proper parsing.
-Suppose we have a general `lineParser`, then a ratingLineParser could be used
-like this:
+Suppose we have a general `nameParser`, then a `ratingParser` could be used like
+this:
```typescript
-const secondLineParser = (firstLine: FirstLine) => {
- return lineParser.filter(
- (val: FirstLine) => firstLine.value.includes(val.name), // β¨
- )
-}
-
-// the parser accepts if a firstLine value is included in the second
-const verifiedParser = lineParser.flatMap(secondLineParser)
+const separator = C.char(':')
+const end = C.char('\n').opt()
+
+const nameParser: SingleParser = C.string('authors')
+ .then(separator.drop())
+ .then(C.letters())
+ .then(end.drop())
+ .last()
+
+const ratingParser = (
+ name: string, // name is from previous parsing
+) =>
+ C.letters()
+ .filter((val: string) => val.includes(name)) // check name match
+ .then(separator.drop())
+ .then(
+ F.not(end)
+ .rep()
+ .map((chars) => chars.join('')),
+ )
+
+const parser = nameParser.flatMap(ratingParser)
+
+const string = `authors:Nicolas\nNicolas:5`
+const value = parser.val(string)
+expect(value.array()).toEqual(['alice', '5'])
```
It can help you to read your document knowing what happen previously
### filter (predicate)
-- difficulty : 1
- To be used once a value is defined
- `predicate` is a function pVal -> boolean
- Check if the stream satisfies the predicate
@@ -252,30 +272,24 @@ It can help you to read your document knowing what happen previously
'expect (filter) to be accepted': function(test) {
test.equal(parser.char("a").filter(a => a === 'a')
- .parse(Streams.ofString("a")).isAccepted(), true, 'should be
- accepted.'); }
+ .parse(Stream.ofChars("a")).isAccepted(), true, 'should be accepted.');
+ }
### match (matchValue)
-- difficulty : 0
- Simplification of `filter()`
- Check if the stream value is equal to the _matchValue_
- //given 123
- N.number().match(123)
+ //given 123
+ N.number().match(123)
### error()
-- difficulty : 0
- Forces an error
- The parser will be `rejected`
-TODO : Is it possible to have a value for this error ? It would give a live hint
-for the writer.
-
### satisfy(predicate)
-- difficulty : 2
- Used internally by higher level functions
- If predicate is true, consumes a element from the stream, and the value is set
to the element
diff --git a/ingest/documentation/parser-extension-functions.md b/ingest/documentation/parser-extension-functions.md
index a89c3f82..064e592c 100644
--- a/ingest/documentation/parser-extension-functions.md
+++ b/ingest/documentation/parser-extension-functions.md
@@ -1,51 +1,31 @@
-Parser Extensions
-=====
-
-
-These functions allow users to quickly find letters or numbers in
-a standard text
+# Parser Extensions
+These functions allow users to quickly find letters or numbers in a standard
+text
### returns()
-* Forces a value
-* Does not consume a character
-
+- Forces a value
+- Does not consume a character
### lazy(parserFunction)
-* The point is that the parserFunction is not called immediately
+- The point is that the parserFunction is not called immediately
- Use `F.lazy( this.text )` and not `F.lazy( this.text() )`
-* The parserFunction will be called when needed
-* One important use is [when dealing with **recursion**](./recursion.html)
-
-
- expression() {
- return F.try(
- number()
- .thenLeft(plus())
- .then(F.lazy( expression ) ) // <-- function is not called
- .map(values => values[0]+values[1])
- )
- .or(this.number());
- }
+- The parserFunction will be called when needed
+- One important use is [when dealing with **recursion**](./recursion.html)
+
+ expression() {
+ return F.try(
+ number()
+ .thenLeft(plus())
+ .then(F.lazy( expression ) ) // <-- function is not called
+ .map(values => values[0]+values[1])
+ )
+ .or(this.number());
+ }
Important : Masala is a streaming parser.
- [The grammar you make a parser for can NOT be left recursive.](https://github.com/d-plaindoux/masala-parser/issues/13) The good news is that any left recursive grammar can be rewritten to a form that masala-parser can handle.
-
-
-
-
-### digit()
-
-
-### letter()
-
-### letters()
-
-
-### string(value)
-
-### lowerCase()
-
-### upperCase()
+[The grammar you make a parser for can NOT be left recursive.](https://github.com/d-plaindoux/masala-parser/issues/13)
+The good news is that any left recursive grammar can be rewritten to a form that
+masala-parser can handle.
diff --git a/ingest/documentation/publish.md b/ingest/documentation/publish.md
index 7267efbb..ac7c0696 100644
--- a/ingest/documentation/publish.md
+++ b/ingest/documentation/publish.md
@@ -1,77 +1,49 @@
-This document is for contributors who want to publish. You must have
-correct ssh key
+This document is for contributors who want to publish. You must have correct ssh
+key
-
-Integration test at lower level
------
+## Integration test at lower level
TL;DR: run `npm run compile` then `node tasks/integrate.js`
-`npm run compile` will compile files with babel; `package.json` says users will import
-
+`npm run compile` will compile files with babel; `package.json` says users will
+import
+
"main": "build/index.js",
-
-
-then `tasks/post-compile` will copy json files needed for unit and performance tests.
-`npm run prepublish` will make a few integration test with this compiled version.
+then `tasks/post-compile` will copy json files needed for unit and performance
+tests.
-These prepublish tests cant be run independently with `node tasks/integrate.js`
+`npm run prepublish` will make a few integration test with this compiled
+version.
+These prepublish tests cant be run independently with `node tasks/integrate.js`
### Make a pre-release to test stuff
-
+
then level-up the version number in package.json
- "version": "0.5.0-alpha1",
-
-then publish
+ "version": "2.1.0-alpha1",
+
+Check compile, and deploy locally to `integration-ts folder`
+
+ npm run cover # check 100% is covered
+ npm run dist # build and copy the files
+ npm run integration # copy files to integration folder and run the integration tests
+
+It should print the test results
+
+then you can publish
npm publish --access=public
-
-
-Check then with integration-npm
-
- cd integration-npm
- # change the dependencie in package.json
- npm install
- # it must load the new published masala
- node integrate.js
- # >>> should write 'true'
- # and: === Post publish Integration SUCCESS ! :) ===
-
-If fail :
-
- # go back to main masala project
- cd ..
- npm unpublish --force # oups !
- # change what is wrong
- # change version to 0.4.0-prerelease2
- npm publish
- # test again integration
-
-If work :
-
-* Set tag on github. On branch master :
-* Change version on package.json
-* commit & push
-* `git tag v0.5.0 master`
-* `git push origin v0.5.0`
-* `npm publish --access=public`
-
-
-
- # careful, especially for major release
- # YOU CANNOT UNPUBLISH easily !!!!
- npm unpublish --force # it would remove a beta, no big deal
- # go back to main masala project
- cd ..
- # change version to to 0.4.0
- npm publish --access=public
-After publishing
----
+If work :
+
+- Set tag on github. On branch master :
+- Change version on package.json
+- commit & push
+- `git tag v2.1.0 master`
+- `git push origin v2.1.0`
+- `npm publish --access=public`
-Every integration tests must be tested with the new npm package
-Then change must be published on Github
-
\ No newline at end of file
+Warning: You cannot unpublish easily after 72 hours, but you can deprecate the
+package
diff --git a/ingest/documentation/recursion.md b/ingest/documentation/recursion.md
index 4c9c5545..baccba81 100644
--- a/ingest/documentation/recursion.md
+++ b/ingest/documentation/recursion.md
@@ -1,94 +1,84 @@
-Recursion
-====
+# Recursion
-Masala-Parser (like Parsec) is a top-down parser and doesn't like [Left Recursion](https://cs.stackexchange.com/a/9971). Furthemore, building naively your
-combinator parser with recursion would make a stack overflow before parsing
+Masala-Parser (like Parsec) is a top-down parser and doesn't like
+[Left Recursion](https://cs.stackexchange.com/a/9971). Furthemore, building
+naively your combinator parser with recursion would make a stack overflow before
+parsing
+## Recursion fail
-Recursion fail
-----
+ const {Stream, F, N, C, X} = require('@masala/parser');
- const {Streams, F, N, C, X} = require('@masala/parser');
-
function A(){
return C.char('A').then(B());
}
-
+
function B(){
return C.char('B').or(A()); // <----- will call A() then B() then A()
}
-
+
console.log('=== Building Parser ====');
const parser = A();
console.log('=== NEVER THERE ====');
- let parsing = parser.parse(Streams.ofString('AAAAAB'));
-
-
-Lazy Recursion
------
+ let parsing = parser.parse(Stream.ofChars('AAAAAB'));
-**Masala-Parser** Comes with a `F.lazy( P )` function that will end the loop while building. It builds the combinator, but P will
-be called only while parsing the stream.
+## Lazy Recursion
+**Masala-Parser** Comes with a `F.lazy( P )` function that will end the loop
+while building. It builds the combinator, but P will be called only while
+parsing the stream.
function A(){
return C.char('A').then(B());
}
-
+
function B(){
return C.char('B').or(F.lazy(A)); <--- A() is not called yet !
}
-
+
console.log('=== Building Parser ====');
const parser = A();
console.log('=== GETTING THERE ====');
- let parsing = parser.parse(Streams.ofString('AAAAAB')); // Accepted :)
-
-Operations
------
+ let parsing = parser.parse(Stream.ofChars('AAAAAB')); // Accepted :)
+
+## Operations
+Operation are logically left-recursive: Let say an expression is `100` or
+`(20*5)`, which is also `((4*5)*5)`, or `(((2*2)*5)*5)` ....
-Operation are logically left-recursive: Let say an expression is `100` or `(20*5)`, which is also `((4*5)*5)`,
- or `(((2*2)*5)*5)` ....
-
-There is a general algorithm that [removes Left recursion](https://en.wikipedia.org/wiki/Left_recursion#Removing_left_recursion).
+There is a general algorithm that
+[removes Left recursion](https://en.wikipedia.org/wiki/Left_recursion#Removing_left_recursion).
It can be written it his PEG form:
-
+
E -> T E'
E' -> + TE' | eps
T -> F T'
T' -> * FT' | eps
F -> NUMBER | ID | ( E )
-Where:
+Where:
-* **E** is an Expression
-* **F** is a terminal expression (Final), or a start for recursion
-* **E'** and **T'** are optional operation made on **F**
-* eps is an empty character
+- **E** is an Expression
+- **F** is a terminal expression (Final), or a start for recursion
+- **E'** and **T'** are optional operation made on **F**
+- eps is an empty character
-Note that there are two distinct level of precedence, as `+` and `*` doesn't have the same level of priority.
-Using only one level of operator, we simplify to:
+Note that there are two distinct level of precedence, as `+` and `*` doesn't
+have the same level of priority. Using only one level of operator, we simplify
+to:
T -> F T'
T' -> operator FT' | eps
F -> NUMBER | ( T )
-Which can be translated in *pseudo masala*:
+Which can be translated in _pseudo masala_:
expr -> terminal then subExpr
subExpr -> (operator then terminal then subExpr ).opt()
terminal -> NUMBER or ( F.lazy(expr) )
-
-
-
-
-It appears that it's simplified with Masala, as you won't have to create a subExpression per operator. Just call
-
- const operator = ()=> C.charIn('+-*/');
-
-
-
+It appears that it's simplified with Masala, as you won't have to create a
+subExpression per operator. Just call
+ const operator = ()=> C.charIn('+-*/');
diff --git a/ingest/documentation/troubleshooting.md b/ingest/documentation/troubleshooting.md
index 5b6caca1..4c211bc5 100644
--- a/ingest/documentation/troubleshooting.md
+++ b/ingest/documentation/troubleshooting.md
@@ -12,13 +12,6 @@ const floorCombinator = C.string('Hello')
.then(C.string('fail'))
.debug('wont be displayed', true)
-const parsing = floorCombinator.parse(Streams.ofChar('Hello World !!!'))
+const parsing = floorCombinator.parse(Stream.ofChars('Hello World !!!'))
assertFalse(parsing.isAccepted(), 'Testing debug')
```
-
-## Stack Overflow
-
-RangeError: Maximum call stack size exceeded at Try.lazyRecoverWith
-(/Users/nicorama/code/products/parsec/parsec/src/lib/data/try.js:83:20)
-
-???
diff --git a/ingest/documentation/tuple.md b/ingest/documentation/tuple.md
index 629da606..d60a41ae 100644
--- a/ingest/documentation/tuple.md
+++ b/ingest/documentation/tuple.md
@@ -2,7 +2,9 @@
## Tuple
-A tuple is a data structure that contains a sequence of elements, each of which can be of a different type. In Masala Parser, Tuples are used to group related data together and are particularly useful for handling parser results.
+A tuple is a data structure that contains a sequence of elements, each of which
+can be of a different type. In Masala Parser, Tuples are used to group related
+data together and are particularly useful for handling parser results.
The very special thing of tuple is that they merge together (see below).
@@ -14,82 +16,85 @@ There are two main ways to create a tuple:
```typescript
// Create an empty tuple
-const emptyTuple: EmptyTuple = tuple();
+const emptyTuple: EmptyTuple = tuple()
// Create a tuple from an array
-const numberTuple: Tuple = tuple([1, 2, 3]);
+const numberTuple: Tuple = tuple([1, 2, 3])
// tuples are immutable. Adding elements will create a new tuple
-const more: Tuple = numberTuple.append(4);
-
+const more: Tuple = numberTuple.append(4)
```
### Tuple Interface
-The Tuple interface provides several methods for working with the contained values:
+The Tuple interface provides several methods for working with the contained
+values:
```typescript
export interface Tuple {
// The underlying array of values
- value: T[];
-
- isEmpty(): boolean;
- size(): number;
-
+ value: T[]
+
+ isEmpty(): boolean
+ size(): number
+
// Get first element (throws if empty)
- single(): T;
-
+ single(): T
+
// Get all elements as an array
- array(): T[];
-
- last(): T;
- first(): T;
- at(index: number): T;
-
+ array(): T[]
+
+ last(): T
+ first(): T
+ at(index: number): T
+
// Join elements with optional separator
- join(separator?: string): string;
-
+ join(separator?: string): string
+
// Append elements or other tuples
- append(neutral: NEUTRAL): this;
- append(other: Tuple): Tuple;
- append(element: Y): Tuple;
+ append(neutral: NEUTRAL): this
+ append(other: Tuple): Tuple
+ append(element: Y): Tuple
}
```
+An EmptyTuple is a Tuple that contains the `NEUTRAL` value. It looks unusual,
+but this information was needed to ensure proper merging.
+
### Examples
```typescript
// Basic tuple operations
-const t1 = tuple(['a', 'b', 'c']);
-console.log(t1.size()); // 3
-console.log(t1.first()); // 'a'
-console.log(t1.last()); // 'c'
-console.log(t1.at(1)); // 'b'
-console.log(t1.join('-')); // 'a-b-c'
+const t1 = tuple(['a', 'b', 'c'])
+console.log(t1.size()) // 3
+console.log(t1.first()) // 'a'
+console.log(t1.last()) // 'c'
+console.log(t1.at(1)) // 'b'
+console.log(t1.join('-')) // 'a-b-c'
// Combining tuples
-const t2 = tuple(['x', 'y']);
-const combined = t1.append(t2);
-console.log(combined.array()); // ['a', 'b', 'c', 'x', 'y']
+const t2 = tuple(['x', 'y'])
+const combined = t1.append(t2)
+console.log(combined.array()) // ['a', 'b', 'c', 'x', 'y']
// Single value access
-const singleTuple = tuple(['value']);
-console.log(singleTuple.single()); // 'value'
+const singleTuple = tuple(['value'])
+console.log(singleTuple.single()) // 'value'
```
## Type inference
-Thought the library is written in pure javascript,
-a .d.ts provides the typing and Tuples are typed as follows:
+Thought the library is written in pure javascript, a .d.ts provides the typing
+and Tuples are typed as follows:
```typescript
-
-const t = tuple(); // Create an EmptyTuple
+const t = tuple() // Create an EmptyTuple
const nTuple = empty.append(2) // Create a Tuple
const mixedTuple = nTuple.append('a') // Create a MixedTuple
```
There are three types of Tuples:
+
1. **EmptyTuple**: Represents an empty tuple with no elements.
2. **Tuple**: Represents a tuple containing elements of type T.
3. **MixedTuple**: Specify the first and last element types
@@ -99,28 +104,27 @@ Defining correctly MixedTuples is important as Parsers often drop most elements.
Types of `first()` and `last()` are also inferred.
```typescript
-
-const first:number = mixedTuple.first() // number 2
+const first: number = mixedTuple.first() // number 2
const last = mixedTuple.last() // string 'a', no need to specify type
-
```
## Tuple merge with Tuple
That's the very power of Tuples.
-* Adding a `number` ot a `Tuple` will still be a `Tuple`.
-* Adding a string to a `Tuple` will create a `MixedTuple`.
-* Adding a `Tuple` to a `Tuple` NOT be a `Tuple>` but still a `Tuple`.
+- Adding a `number` ot a `Tuple` will still be a `Tuple`.
+- Adding a string to a `Tuple` will create a
+ `MixedTuple`.
+- Adding a `Tuple` to a `Tuple` NOT be a
+ `Tuple>` but still a `Tuple`.
```typescript
-const tuple1:Tuple = tuple([1, 2, 3])
-const tuple2:Tuple = tuple([4, 5, 6])
-const merged:Tuple = tuple1.append(tuple2)
+const tuple1: Tuple = tuple([1, 2, 3])
+const tuple2: Tuple = tuple([4, 5, 6])
+const merged: Tuple = tuple1.append(tuple2)
expect(merged.first()).toBe(1)
expect(merged.last()).toBe(6)
expect(merged.at(3)).toBe(4)
```
Therefore, a Tuple merged with an EmptyTuple will still be a Tuple.
-
diff --git a/ingest/examples/1-easy/chars.spec.ts b/ingest/examples/1-easy/chars.spec.ts
index 769c059e..5a2b9381 100644
--- a/ingest/examples/1-easy/chars.spec.ts
+++ b/ingest/examples/1-easy/chars.spec.ts
@@ -1,12 +1,12 @@
import { describe, it, expect } from 'vitest'
-import { Streams, F, C } from '@masala/parser'
+import { Stream, F, C } from '@masala/parser'
/**
* Created by Nicolas Zozol on 05/11/2017.
*/
describe('Character parsers', () => {
it('should parse sequential characters followed by EOS', () => {
- const stream = Streams.ofChars('abc')
+ const stream = Stream.ofChars('abc')
const charsParser = C.char('a')
.then(C.char('b'))
.then(C.char('c'))
diff --git a/ingest/examples/1-easy/floor.spec.ts b/ingest/examples/1-easy/floor.spec.ts
index 500d2d21..28151cf9 100644
--- a/ingest/examples/1-easy/floor.spec.ts
+++ b/ingest/examples/1-easy/floor.spec.ts
@@ -1,18 +1,20 @@
import { describe, it, expect } from 'vitest'
-import { Streams, C, N } from '@masala/parser'
+import { Stream, C, N } from '@masala/parser'
-describe('Floor combinator', () => {
- it('should parse a number between pipes and floor it', () => {
- let stream = Streams.ofChars('|4.6|')
- const floorCombinator = C.char('|')
- .drop()
- .then(N.number()) // we have ['|',4.6], we keep 4.6
- .then(C.char('|').drop()) // we have [4.6, '|'], we keep [4.6]
+describe('Absolute parser', () => {
+ it('should parse a number between pipes and make it positive', () => {
+ // We parse the mathematical absolute expression: |-4.6|
+ let stream = Stream.ofChars('|-4.6|')
+ const absoluteParser = C.char('|')
+ .then(N.number())
+ .last() // we had ['|',-4.6], we keep -4.6
+ // we take and immediately drop '|'
+ .then(C.char('|').drop()) // we now have [-4.6]
.single()
- .map((x) => Math.floor(x))
+ .map((x) => Math.abs(x)) // we map -4.6 to 4.6
- // Parsec needs a stream of characters
- let parsing = floorCombinator.parse(stream)
+ // Masala needs a stream of characters
+ let parsing = absoluteParser.parse(stream)
expect(parsing.value).toBe(4)
})
})
diff --git a/ingest/examples/1-easy/hello-something.spec.ts b/ingest/examples/1-easy/hello-something.spec.ts
index be9ee5db..3cbe858c 100644
--- a/ingest/examples/1-easy/hello-something.spec.ts
+++ b/ingest/examples/1-easy/hello-something.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, C } from '@masala/parser'
+import { Stream, C } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Hello Something Parser', () => {
@@ -10,7 +10,7 @@ describe('Hello Something Parser', () => {
.then(C.letter().rep()) // keeping repeated ascii letters
.then(C.char("'").drop()) // keeping previous letters
- const parsing = helloParser.parse(Streams.ofChars("Hello 'World'"))
+ const parsing = helloParser.parse(Stream.ofChars("Hello 'World'"))
// C.letter.rep() will giv a array of letters
expect(parsing.value.array()).toEqual(['W', 'o', 'r', 'l', 'd'])
})
@@ -25,7 +25,7 @@ describe('Hello Something Parser', () => {
// Note that helloParser will not reach the end of the stream; it will stop at the space after People
const peopleParsing = helloParser.parse(
- Streams.ofChars("Hello 'People' in 2017"),
+ Stream.ofChars("Hello 'People' in 2017"),
)
expect(peopleParsing.value.join('')).toBe('People')
diff --git a/ingest/examples/1-easy/number.spec.ts b/ingest/examples/1-easy/number.spec.ts
index 49a74f16..59259252 100644
--- a/ingest/examples/1-easy/number.spec.ts
+++ b/ingest/examples/1-easy/number.spec.ts
@@ -1,11 +1,11 @@
-import { Streams, F, N } from '@masala/parser'
+import { Stream, F, N } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Number parser', () => {
it('should parse a number followed by EOS', () => {
// Parsec needs a stream of characters
const document = '12'
- const s = Streams.ofChars(document)
+ const s = Stream.ofChars(document)
// numberLitteral defines any int or float number
// We expect a number, then eos: End Of Stream
diff --git a/ingest/examples/1-easy/occurence.spec.ts b/ingest/examples/1-easy/occurence.spec.ts
index 810a2bb6..e6747286 100644
--- a/ingest/examples/1-easy/occurence.spec.ts
+++ b/ingest/examples/1-easy/occurence.spec.ts
@@ -1,10 +1,10 @@
-import { Streams, C, N } from '@masala/parser'
+import { Stream, C, N } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Occurrence parser', () => {
it('should parse 5 occurrences of a digit', () => {
let combinator = N.digit().occurrence(5)
- let response = combinator.parse(Streams.ofChars('55555'))
+ let response = combinator.parse(Stream.ofChars('55555'))
expect(response.value.size()).toBe(5)
})
@@ -12,7 +12,7 @@ describe('Occurrence parser', () => {
it('should parse 3 occurrences of a digit followed by a number', () => {
// we are looking for 5,5,5 then 55
let combinator = N.digit().occurrence(3).then(N.number())
- let response = combinator.parse(Streams.ofChars('55555'))
+ let response = combinator.parse(Stream.ofChars('55555'))
expect(response.isAccepted()).toBe(true)
expect(response.value.last()).toBe(55)
@@ -23,7 +23,7 @@ describe('Occurrence parser', () => {
* Occurence with a Tuple parser
*/
let parser = C.char('a').then(C.char('b')).occurrence(3)
- let resp = parser.parse(Streams.ofChars('ababab'))
+ let resp = parser.parse(Stream.ofChars('ababab'))
// Expecting a structure like [['a','b'], ['a','b'], ['a','b']]
expect(resp.isAccepted()).toBe(true)
@@ -41,7 +41,7 @@ describe('Occurrence parser', () => {
* Occurence with a Tuple parser
*/
let parser = C.char('a').then(C.char('b')).occurrence(3)
- let resp = parser.parse(Streams.ofChars('ababab'))
+ let resp = parser.parse(Stream.ofChars('ababab'))
// Expecting a structure like [['a','b'], ['a','b'], ['a','b']]
expect(resp.isAccepted()).toBe(true)
diff --git a/ingest/examples/1-easy/response.spec.ts b/ingest/examples/1-easy/response.spec.ts
index d39e39df..f0e5f1c4 100644
--- a/ingest/examples/1-easy/response.spec.ts
+++ b/ingest/examples/1-easy/response.spec.ts
@@ -1,9 +1,9 @@
-import { Streams, C } from '@masala/parser'
+import { Stream, C } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Parser Response', () => {
it('should handle fully accepted response', () => {
- let response = C.char('a').rep().parse(Streams.ofChars('aaaa'))
+ let response = C.char('a').rep().parse(Stream.ofChars('aaaa'))
expect(response.value.join('')).toBe('aaaa')
expect(response.offset).toBe(4)
expect(response.isAccepted()).toBe(true)
@@ -12,7 +12,7 @@ describe('Parser Response', () => {
it('should handle partially accepted response', () => {
// Partially accepted
- let response = C.char('a').rep().parse(Streams.ofChars('aabb'))
+ let response = C.char('a').rep().parse(Stream.ofChars('aabb'))
expect(response.value.join('')).toBe('aa')
expect(response.offset).toBe(2)
expect(response.isAccepted()).toBe(true)
diff --git a/ingest/examples/2-medium/f-layer.spec.ts b/ingest/examples/2-medium/f-layer.spec.ts
index 1f26a8c7..c66a3587 100644
--- a/ingest/examples/2-medium/f-layer.spec.ts
+++ b/ingest/examples/2-medium/f-layer.spec.ts
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
-import { Streams, F, C, N } from '@masala/parser'
+import { Stream, F, C, N } from '@masala/parser'
describe('F Layer Combinators', () => {
it('should parse alternatives using try and or', () => {
@@ -7,7 +7,7 @@ describe('F Layer Combinators', () => {
.or(C.string('hello'))
.or(C.string('goodbye'))
- let response = combinator.parse(Streams.ofChars('goodbye'))
+ let response = combinator.parse(Stream.ofChars('goodbye'))
expect(response.isAccepted()).toBe(true)
// Check the value based on the expected type if needed
// expect(response.value).toBe('goodbye');
@@ -20,7 +20,7 @@ describe('F Layer Combinators', () => {
const successInput = 'aa'
const layer = F.layer(first).and(second)
- let layerResponse = layer.parse(Streams.ofChars(successInput))
+ let layerResponse = layer.parse(Stream.ofChars(successInput))
expect(layerResponse.isAccepted()).toBe(true)
// Add expectation for the value if needed, depends on F.layer logic
// e.g., expect(layerResponse.value)... depending on what F.layer returns
diff --git a/ingest/examples/2-medium/filter-match.spec.ts b/ingest/examples/2-medium/filter-match.spec.ts
index f7781be6..169def27 100644
--- a/ingest/examples/2-medium/filter-match.spec.ts
+++ b/ingest/examples/2-medium/filter-match.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, N } from '@masala/parser'
+import { Stream, N } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Filter and Match Combinators', () => {
@@ -7,7 +7,7 @@ describe('Filter and Match Combinators', () => {
.filter((m) => m >= 5)
.rep()
// The parser will accept the first 3 numbers (5, 6, 7)
- let response = combinator.parse(Streams.ofChars('5672'))
+ let response = combinator.parse(Stream.ofChars('5672'))
expect(response.isAccepted()).toBe(true)
expect(response.value.size()).toBe(3)
// Check the actual values if needed
@@ -21,16 +21,16 @@ describe('Filter and Match Combinators', () => {
let parser = N.number().match(55)
// Test case 1: exact match
- let res1 = parser.parse(Streams.ofChars('55'))
+ let res1 = parser.parse(Stream.ofChars('55'))
expect(res1.isAccepted()).toBe(true)
expect(res1.value).toBe(55)
// Test case 2: non-match (extra digit)
- let res2 = parser.parse(Streams.ofChars('555'))
+ let res2 = parser.parse(Stream.ofChars('555'))
expect(res2.isAccepted()).toBe(false)
// Test case 3: non-match (different number)
- let res3 = parser.parse(Streams.ofChars('56'))
+ let res3 = parser.parse(Stream.ofChars('56'))
expect(res3.isAccepted()).toBe(false)
})
})
diff --git a/ingest/examples/2-medium/flat-map.spec.ts b/ingest/examples/2-medium/flat-map.spec.ts
index 916821b2..06edf5b0 100644
--- a/ingest/examples/2-medium/flat-map.spec.ts
+++ b/ingest/examples/2-medium/flat-map.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, C, N } from '@masala/parser'
+import { Stream, C, N, SingleParser, F } from '@masala/parser'
import { describe, it, expect } from 'vitest'
// Next char must be the double of the previous
@@ -11,19 +11,53 @@ describe('FlatMap Combinator', () => {
const combinator = N.digit().flatMap(doubleNumber)
// Test case 1: Successful parse (1 followed by 2)
- let response1 = combinator.parse(Streams.ofChars('12'))
+ let response1 = combinator.parse(Stream.ofChars('12'))
expect(response1.isAccepted()).toBe(true)
// flatMap typically combines results. Check how the library handles this.
// Assuming it keeps the second parser's result: expect(response1.value.join('')).toBe('2');
// Or if it combines: expect(response1.value.array()).toEqual([1, '2']);
// Test case 2: Failed parse (1 followed by 3, expected 2)
- let response2 = combinator.parse(Streams.ofChars('13'))
+ let response2 = combinator.parse(Stream.ofChars('13'))
expect(response2.isAccepted()).toBe(false)
// Test case 3: Successful parse (4 followed by 8)
- let response3 = combinator.parse(Streams.ofChars('48'))
+ let response3 = combinator.parse(Stream.ofChars('48'))
expect(response3.isAccepted()).toBe(true)
// Assuming it keeps the second parser's result: expect(response3.value.join('')).toBe('8');
})
+
+ it('should parse lines', () => {
+ const separator = C.char(':')
+ const end = C.char('\n').opt()
+
+ const nameParser: SingleParser = C.string('authors')
+ .then(separator.drop())
+ .then(C.letters())
+ .then(end.drop())
+ .last()
+
+ const ratingParser = (name: string) =>
+ C.letters()
+ .filter((val: string) => val.includes(name))
+ .then(separator.drop())
+ .then(
+ F.not(end)
+ .rep()
+ .map((chars) => chars.join('')),
+ )
+
+ const parser = nameParser.flatMap(ratingParser)
+
+ const string = `authors:alice\nalice:5`
+ const value = parser.val(string)
+ expect(value.array()).toEqual(['alice', '5'])
+
+ /*const ratingParser = (nameParser: SingleParser) => {
+ return nameParser.filter(
+ (val: string) => val.includes(val.name), // β¨
+ )
+ }
+ */
+ })
})
diff --git a/ingest/examples/2-medium/real-world-flatmap.spec.ts b/ingest/examples/2-medium/real-world-flatmap.spec.ts
index 37d629fd..dce8db66 100644
--- a/ingest/examples/2-medium/real-world-flatmap.spec.ts
+++ b/ingest/examples/2-medium/real-world-flatmap.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, C, N, F, SingleParser, TupleParser } from '@masala/parser'
+import { Stream, C, N, F, SingleParser, TupleParser } from '@masala/parser'
import { describe, it, expect } from 'vitest'
interface FirstLine {
@@ -45,7 +45,7 @@ const verifiedParser = lineParser.flatMap(secondLineParser)
describe('Flatmap real life case', () => {
it('should parse a line', () => {
- const stream = Streams.ofChars(document)
+ const stream = Stream.ofChars(document)
const parsing = verifiedParser.parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toEqual({
diff --git a/ingest/examples/brainfuck/compiler.spec.ts b/ingest/examples/brainfuck/compiler.spec.ts
index 6085aa3d..7939b1ef 100644
--- a/ingest/examples/brainfuck/compiler.spec.ts
+++ b/ingest/examples/brainfuck/compiler.spec.ts
@@ -5,7 +5,7 @@ import {
IParser,
Option,
SingleParser,
- Streams,
+ Stream,
Tuple,
tuple,
TupleParser,
@@ -105,7 +105,7 @@ function brainfuck(program: string) {
const parser = createParser()
- let response = parser.parse(Streams.ofChars(program))
+ let response = parser.parse(Stream.ofChars(program))
interpretAll(response.value)
@@ -180,7 +180,7 @@ describe('Brainfuck Interpreter', () => {
const program = '+++>+'
resetState()
const parser = createParser()
- let response = parser.parse(Streams.ofChars(program))
+ let response = parser.parse(Stream.ofChars(program))
interpretAll(response.value)
expect(memory.slice(0, max + 1)).toEqual([3, 1])
@@ -192,7 +192,7 @@ describe('Brainfuck Interpreter', () => {
const program = '+++[>+<-]'
resetState()
const parser = createParser()
- let response = parser.parse(Streams.ofChars(program))
+ let response = parser.parse(Stream.ofChars(program))
interpretAll(response.value)
// Memory: [0, 3] (values swapped), Pointer: 0
@@ -207,7 +207,7 @@ describe('Brainfuck Interpreter', () => {
'++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.'
resetState()
const parser = createParser()
- let response = parser.parse(Streams.ofChars(program))
+ let response = parser.parse(Stream.ofChars(program))
interpretAll(response.value)
expect(output).toBe('Hello World!\n')
diff --git a/ingest/examples/chars/charIn-notChar.spec.ts b/ingest/examples/chars/charIn-notChar.spec.ts
index 67238824..d84c7267 100644
--- a/ingest/examples/chars/charIn-notChar.spec.ts
+++ b/ingest/examples/chars/charIn-notChar.spec.ts
@@ -1,11 +1,11 @@
-import { Streams, C } from '@masala/parser'
+import { Stream, C } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Character Combinators (charIn, charNotIn)', () => {
it('charNotIn should parse characters not in the specified set', () => {
let combinator = C.charNotIn('abc').rep()
// will accept x, y, and z but will stop at 'b'
- let response = combinator.parse(Streams.ofChars('xyzb'))
+ let response = combinator.parse(Stream.ofChars('xyzb'))
expect(response.isAccepted()).toBe(true)
expect(response.offset).toBe(3)
@@ -14,7 +14,7 @@ describe('Character Combinators (charIn, charNotIn)', () => {
it('charIn should parse characters within the specified set until EOS', () => {
let combinator = C.charIn('abc').rep().thenEos()
- let response = combinator.parse(Streams.ofChars('acbaba'))
+ let response = combinator.parse(Stream.ofChars('acbaba'))
expect(response.isAccepted()).toBe(true)
expect(response.value.join('')).toBe('acbaba')
diff --git a/ingest/examples/chars/letter-letterAs.spec.ts b/ingest/examples/chars/letter-letterAs.spec.ts
index bda65c64..671921b3 100644
--- a/ingest/examples/chars/letter-letterAs.spec.ts
+++ b/ingest/examples/chars/letter-letterAs.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, C } from '@masala/parser'
+import { Stream, C } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Character Combinators (letters, lowerCase, notString)', () => {
@@ -13,13 +13,12 @@ describe('Character Combinators (letters, lowerCase, notString)', () => {
.then(C.notChar('.').rep()) // Parses until the end
}
- let stream = Streams.ofChars(inputString)
+ let stream = Stream.ofChars(inputString)
let parsing = combinator().parse(stream)
expect(parsing.isAccepted()).toBe(true)
const structure = parsing.value.array() as string[]
expect(structure[0]).toBe('The') // Check the number of parsed elements
- console.log(parsing.value)
expect(parsing.value.join('')).toBe(
'The quick brown fox jumps over the lazy dog',
) // Check the collected value
diff --git a/ingest/examples/flow/nop-any-eos.spec.ts b/ingest/examples/flow/nop-any-eos.spec.ts
index 3788ed7a..27e9af71 100644
--- a/ingest/examples/flow/nop-any-eos.spec.ts
+++ b/ingest/examples/flow/nop-any-eos.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, F, C } from '@masala/parser'
+import { Stream, F, C } from '@masala/parser'
import { describe, it, expect } from 'vitest'
function day() {
@@ -19,7 +19,7 @@ function combinator() {
return F.any().then(day()).then(F.nop()).then(F.any()).eos()
}
-let stream = Streams.ofChars(string)
+let stream = Stream.ofChars(string)
let parsing = combinator().parse(stream)
describe('Flow Combinators (nop, any, eos)', () => {
diff --git a/ingest/examples/flow/not.spec.ts b/ingest/examples/flow/not.spec.ts
index e48d237e..84e7ea99 100644
--- a/ingest/examples/flow/not.spec.ts
+++ b/ingest/examples/flow/not.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, F, C } from '@masala/parser'
+import { Stream, F, C } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Flow Combinator (not)', () => {
@@ -21,7 +21,7 @@ describe('Flow Combinator (not)', () => {
const inputString = 'Xabx' // X matches not(day), fails not(a), fails not(day)
- let stream = Streams.ofChars(inputString)
+ let stream = Stream.ofChars(inputString)
let parsing1 = F.not(day()).parse(stream)
expect(parsing1.isAccepted(), "F.not(day()) on 'X'").toBe(true)
diff --git a/ingest/examples/flow/startWith-moveUntil-dropTo.spec.ts b/ingest/examples/flow/startWith-moveUntil-dropTo.spec.ts
index 8c803c43..db9be20a 100644
--- a/ingest/examples/flow/startWith-moveUntil-dropTo.spec.ts
+++ b/ingest/examples/flow/startWith-moveUntil-dropTo.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, F, C } from '@masala/parser'
+import { Stream, F, C } from '@masala/parser'
import { describe, it, expect } from 'vitest'
const string = 'The quick brown fox jumps over the lazy dog'
@@ -14,7 +14,7 @@ describe('Flow Combinators (startWith, moveUntil, dropTo)', () => {
.then(F.dropTo('dog'))
}
- let stream = Streams.ofChars(inputString)
+ let stream = Stream.ofChars(inputString)
let parsing = combinator().parse(stream)
expect(parsing.isAccepted()).toBe(true)
diff --git a/ingest/examples/flow/try-with-no-or.spec.ts b/ingest/examples/flow/try-with-no-or.spec.ts
index 5f8aa498..3ada5bb6 100644
--- a/ingest/examples/flow/try-with-no-or.spec.ts
+++ b/ingest/examples/flow/try-with-no-or.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, F, C } from '@masala/parser'
+import { Stream, F, C } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Flow Combinators (try, opt)', () => {
@@ -28,24 +28,26 @@ describe('Flow Combinators (try, opt)', () => {
// Parses optional 'xyz'
function optAlternative() {
- return C.string('xyz').opt().debug('opt')
+ return C.string('xyz').opt()
}
function combinator() {
- return day()
- .debug('day') // Parses TUESDAY
- .then(blank().rep()) // Parses spaces
+ return (
+ day()
+ //.debug('day') // Parses TUESDAY
+ .then(blank().rep()) // Parses spaces
- .then(separator().debug('sep')) // Parses '---'
- .then(optAlternative().map((x) => x.orElse('42')))
- .debug('afterOPt') // Parses optional 'xyz', fails, returns '12'
- .then(emptyTry().or(day()).debug('emptyTry')) // Tries 'xyz', fails, backtracks (consumes nothing)
+ .then(separator()) // Parses '---'
+ .then(optAlternative().map((x) => x.orElse('42')))
+ //.debug('afterOPt') // Parses optional 'xyz', fails, returns '12'
+ .then(emptyTry().or(day()))
+ ) // Tries 'xyz', fails, backtracks (consumes nothing)
}
const inputString = 'TUESDAY ---FRIDAY' // Simplified input for clarity
// Original: 'TUESDAY THURSDAY TUESDAY ---FRIDAY'; causes issues with rep()
- let stream = Streams.ofChars(inputString)
+ let stream = Stream.ofChars(inputString)
let parsing = combinator().parse(stream)
// Based on the original assertFalse(parsing.isAccepted());
@@ -64,7 +66,7 @@ describe('Flow Combinators (try, opt)', () => {
// Let's test the original failure case logic - why did it fail?
const originalString = 'TUESDAY THURSDAY TUESDAY ---FRIDAY'
- let originalStream = Streams.ofChars(originalString)
+ let originalStream = Stream.ofChars(originalString)
let originalParsing = combinator().parse(originalStream)
// Perhaps the blank().rep() was too greedy?
expect(originalParsing.isAccepted()).toBe(false) // Confirming original behavior
diff --git a/ingest/examples/lazy/transmission.spec.ts b/ingest/examples/lazy/transmission.spec.ts
index 3d83a207..2b416353 100644
--- a/ingest/examples/lazy/transmission.spec.ts
+++ b/ingest/examples/lazy/transmission.spec.ts
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
-import { Streams, F, C, SingleParser, Tuple } from '@masala/parser'
+import { Stream, F, C, SingleParser, Tuple } from '@masala/parser'
describe('Lazy Combinator with Recursion', () => {
it('should handle recursive parsing with F.lazy', () => {
@@ -29,7 +29,7 @@ describe('Lazy Combinator with Recursion', () => {
// Input: AAA B
const str = 'AAAB'
- const stream = Streams.ofChars(str)
+ const stream = Stream.ofChars(str)
const parsing = parser.parse(stream)
// A('a') -> C.char('A').rep() parses 'AAA'
diff --git a/ingest/examples/markdown/lib/document-parser.ts b/ingest/examples/markdown/lib/document-parser.ts
index 61a0ee77..b18ee540 100644
--- a/ingest/examples/markdown/lib/document-parser.ts
+++ b/ingest/examples/markdown/lib/document-parser.ts
@@ -2,7 +2,7 @@
* Created by Simon on 16/12/2016.
*/
-import { Streams, F, Stream, GenLex, TupleParser } from '@masala/parser'
+import { F, GenLex, TupleParser } from '@masala/parser'
import { paragraph } from './text-parser.js'
import { title } from './title-parser.js'
diff --git a/ingest/examples/markdown/lib/front-matter.ts b/ingest/examples/markdown/lib/front-matter.ts
index 29739f4f..246ae464 100644
--- a/ingest/examples/markdown/lib/front-matter.ts
+++ b/ingest/examples/markdown/lib/front-matter.ts
@@ -1,4 +1,4 @@
-import { F, C, Streams, Tuple, TupleParser } from '@masala/parser'
+import { F, C, Stream, Tuple, TupleParser } from '@masala/parser'
import { SingleParser } from '@masala/parser'
interface FrontMatterLine {
diff --git a/ingest/examples/markdown/test/bullet-test.ts b/ingest/examples/markdown/test/bullet-test.ts
index 2255fe21..e8abbf27 100644
--- a/ingest/examples/markdown/test/bullet-test.ts
+++ b/ingest/examples/markdown/test/bullet-test.ts
@@ -5,7 +5,7 @@ import {
assertTrue,
} from '../../../assert.js'
import { bullet, bulletBlock } from '../lib/bullet-parser.js'
-import { F, GenLex, Streams } from '@masala/parser'
+import { F, GenLex, Stream } from '@masala/parser'
import { eol } from '../lib/token.js'
export const bulletsTests = {
@@ -82,7 +82,7 @@ export const bulletsTests = {
const text = `* This is a bullet`
const line = text + '\n'
- let response = bullet().parse(Streams.ofChars(line))
+ let response = bullet().parse(Stream.ofChars(line))
assertTrue(response.isAccepted())
assertEquals(response.offset, text.length)
},
@@ -91,7 +91,7 @@ export const bulletsTests = {
const block = `* This is first bullet
* This is another bullet`
- let response = bulletBlock().parse(Streams.ofChars(block))
+ let response = bulletBlock().parse(Stream.ofChars(block))
assertTrue(response.isAccepted())
assertTrue(response.isEos())
},
@@ -103,14 +103,14 @@ export const bulletsTests = {
const text = block + '\n'
- let response = bulletBlock().parse(Streams.ofChars(text))
+ let response = bulletBlock().parse(Stream.ofChars(text))
assertTrue(response.isAccepted())
assertEquals(block.length, response.offset)
assertFalse(response.isEos())
let otherResponse = bulletBlock()
.then(eol())
- .parse(Streams.ofChars(text))
+ .parse(Stream.ofChars(text))
assertTrue(otherResponse.isAccepted())
assertTrue(otherResponse.isEos())
},
diff --git a/ingest/examples/markdown/test/front-matter.spec.ts b/ingest/examples/markdown/test/front-matter.spec.ts
index 600c57e7..55fa664b 100644
--- a/ingest/examples/markdown/test/front-matter.spec.ts
+++ b/ingest/examples/markdown/test/front-matter.spec.ts
@@ -1,10 +1,10 @@
import { describe, it, expect } from 'vitest'
import { frontMatterParser } from '../lib/front-matter.js'
-import { Streams } from '@masala/parser'
+import { Stream } from '@masala/parser'
describe('Front Matter Parser', () => {
it('should parse a single line of front matter', () => {
- const input = Streams.ofChars('title: My Document\n')
+ const input = Stream.ofChars('title: My Document\n')
const result = frontMatterParser.parse(input)
expect(result.isAccepted()).toBe(true)
expect(result.value.array()).toEqual([
@@ -13,7 +13,7 @@ describe('Front Matter Parser', () => {
})
it('should parse multiple lines of front matter', () => {
- const input = Streams.ofChars(`title: My Document
+ const input = Stream.ofChars(`title: My Document
author: John Doe
date: 2024-03-20
`)
@@ -27,20 +27,20 @@ date: 2024-03-20
})
it('should handle empty values', () => {
- const input = Streams.ofChars('title:\n')
+ const input = Stream.ofChars('title:\n')
const result = frontMatterParser.parse(input)
expect(result.isAccepted()).toBe(true)
expect(result.value.array()).toEqual([{ name: 'title', value: '' }])
})
it('should reject invalid identifiers', () => {
- const input = Streams.ofChars('123title: Invalid\n')
+ const input = Stream.ofChars('123title: Invalid\n')
const result = frontMatterParser.parse(input)
expect(result.isAccepted()).toBe(false)
})
it('should handle multiple newlines between entries', () => {
- const input = Streams.ofChars(`title: My Document
+ const input = Stream.ofChars(`title: My Document
author: John Doe
diff --git a/ingest/examples/markdown/test/text-test.ts b/ingest/examples/markdown/test/text-test.ts
index 44e441bf..8327814d 100644
--- a/ingest/examples/markdown/test/text-test.ts
+++ b/ingest/examples/markdown/test/text-test.ts
@@ -1,6 +1,6 @@
import { assertDeepEquals, assertEquals, assertTrue } from '../../../assert.js'
import { paragraph } from '../lib/text-parser.js'
-import { Streams } from '@masala/parser'
+import { Stream } from '@masala/parser'
export const textTests = {
'test empty text': function () {
@@ -93,7 +93,7 @@ export const textTests = {
{ type: 'text', text: '.' },
],
}
- let response = paragraph().parse(Streams.ofChars(text))
+ let response = paragraph().parse(Stream.ofChars(text))
assertDeepEquals(response.value, expectedComplexParagraph)
assertEquals(response.offset, complexParagraph.length)
diff --git a/ingest/examples/markdown/test/title-test.ts b/ingest/examples/markdown/test/title-test.ts
index a7e44d1b..ad49b986 100644
--- a/ingest/examples/markdown/test/title-test.ts
+++ b/ingest/examples/markdown/test/title-test.ts
@@ -4,7 +4,7 @@
import { title } from '../lib/title-parser.js'
import { assertDeepEquals, assertEquals, assertTrue } from '../../../assert.js'
import { bullet } from '../lib/bullet-parser.js'
-import { Streams } from '@masala/parser'
+import { Stream } from '@masala/parser'
export const titleTests = {
'test level1': function () {
@@ -113,7 +113,7 @@ export const titleTests = {
const text = `### This is a title`
const line = text + '\n'
- let response = title().parse(Streams.ofChars(line))
+ let response = title().parse(Stream.ofChars(line))
assertTrue(response.isAccepted())
assertEquals(response.offset, text.length)
},
@@ -122,7 +122,7 @@ export const titleTests = {
const text = `This is a title\n------`
const line = text + '\n'
- let response = title().parse(Streams.ofChars(line))
+ let response = title().parse(Stream.ofChars(line))
assertTrue(response.isAccepted())
assertEquals(response.offset, text.length)
},
diff --git a/ingest/examples/operations/plus-minus.spec.ts b/ingest/examples/operations/plus-minus.spec.ts
index ea8bfc22..818dc95f 100644
--- a/ingest/examples/operations/plus-minus.spec.ts
+++ b/ingest/examples/operations/plus-minus.spec.ts
@@ -1,4 +1,4 @@
-import { Streams, F, C, SingleParser, Option, Tuple } from '@masala/parser'
+import { Stream, F, C, SingleParser, Option, Tuple } from '@masala/parser'
import { describe, it, expect } from 'vitest'
/*
@@ -112,7 +112,7 @@ function combinator() {
describe('Expression Parser (+, *)', () => {
// Helper function to run the parser and return the result value or throw error
const parseExpr = (input: string): number => {
- let stream = Streams.ofChars(input)
+ let stream = Stream.ofChars(input)
let response = combinator().parse(stream)
if (response.isAccepted() && response.isEos()) {
return response.value
diff --git a/ingest/examples/opt-and-rep/forever-parse.spec.ts b/ingest/examples/opt-and-rep/forever-parse.spec.ts
new file mode 100644
index 00000000..91f4c941
--- /dev/null
+++ b/ingest/examples/opt-and-rep/forever-parse.spec.ts
@@ -0,0 +1,43 @@
+import { describe, it, expect } from 'vitest'
+import { Worker } from 'node:worker_threads'
+
+/**
+ * Nested .optrep() is known to loop forever. We detect that by
+ * running the parse in a worker-thread and terminating it after
+ * 300 ms. The test EXPECTS that termination (i.e. the promise
+ * rejects with βtimeoutβ); if the parser ever returns normally
+ * the assertion will fail β which is exactly what we want once
+ * the bug is fixed.
+ */
+describe('should parse optrep().optrep() forever, but we stop it', () => {
+ it('should throw after 0.3 s when nested optrep() hangs', async () => {
+ const worker = new Worker(
+ new URL('./forever-parse.worker.mjs', import.meta.url),
+ // { type: 'module' }
+ )
+
+ const parsePromise = new Promise((_, reject) => {
+ const timer = setTimeout(() => {
+ worker.terminate().then(() => reject(new Error('timeout')))
+ }, 200)
+
+ worker.once('message', (msg) => {
+ clearTimeout(timer)
+ reject(
+ new Error(
+ typeof msg === 'string'
+ ? `unexpected worker completion: ${msg}`
+ : `worker error: ${msg.error}`,
+ ),
+ )
+ })
+
+ worker.once('error', (err) => {
+ clearTimeout(timer)
+ reject(err)
+ })
+ })
+
+ await expect(parsePromise).rejects.toThrow('timeout')
+ }, 1_000)
+})
diff --git a/ingest/examples/opt-and-rep/forever-parse.worker.mjs b/ingest/examples/opt-and-rep/forever-parse.worker.mjs
new file mode 100644
index 00000000..78e587e1
--- /dev/null
+++ b/ingest/examples/opt-and-rep/forever-parse.worker.mjs
@@ -0,0 +1,12 @@
+import { parentPort } from 'node:worker_threads'
+import { C, N, Stream } from '@masala/parser'
+
+const blocks = N.number().then(C.char('/')).optrep().optrep() // the forever parse
+
+try {
+ const stream = Stream.ofChars('1/2/3/4/5/')
+ blocks.parse(stream)
+ parentPort.postMessage('finished') // should never happen
+} catch (e) {
+ parentPort.postMessage({ error: e?.message ?? String(e) })
+}
diff --git a/ingest/examples/opt-and-rep/opt.spec.ts b/ingest/examples/opt-and-rep/opt.spec.ts
index d7a39bcb..64427f28 100644
--- a/ingest/examples/opt-and-rep/opt.spec.ts
+++ b/ingest/examples/opt-and-rep/opt.spec.ts
@@ -1,4 +1,4 @@
-import { C, N, Tuple, Streams } from '@masala/parser'
+import { C, N, Tuple, Stream } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('Optional parser', () => {
@@ -21,7 +21,7 @@ describe('Optional parser', () => {
const n = C.char('z')
const mixed = c.then(n)
- const stream = Streams.ofChars('zNOT_REACHED')
+ const stream = Stream.ofChars('zNOT_REACHED')
const response = mixed.parse(stream) // not 'a', so we have 'b' as default value
const data = response.value
diff --git a/ingest/examples/opt-and-rep/optrep.spec.ts b/ingest/examples/opt-and-rep/optrep.spec.ts
index f1700b5a..ae3cf725 100644
--- a/ingest/examples/opt-and-rep/optrep.spec.ts
+++ b/ingest/examples/opt-and-rep/optrep.spec.ts
@@ -1,4 +1,4 @@
-import { C, N, Streams } from '@masala/parser'
+import { C, N, Stream } from '@masala/parser'
import { describe, it, expect } from 'vitest'
import { Worker } from 'worker_threads'
@@ -57,68 +57,3 @@ describe('Optrep parser', () => {
})
})
})
-
-describe('optrep β infinite-loop guard', () => {
- /**
- * Nested .optrep() is known to loop forever. We detect that by
- * running the parse in a worker-thread and terminating it after
- * 300 ms. The test EXPECTS that termination (i.e. the promise
- * rejects with βtimeoutβ); if the parser ever returns normally
- * the assertion will fail β which is exactly what we want once
- * the bug is fixed.
- */
- it('should throw after 0.3 s when nested optrep() hangs', async () => {
- // Worker source code as a string so we can `eval` it.
- const workerSource = `
- const { parentPort } = require('worker_threads');
- const { C, N, Streams } = require('@masala/parser');
-
- const blocks = N.number()
- .then(C.char("/"))
- .optrep()
- .optrep(); // β¨ THE FOREVER PARSE
-
- // If it *ever* finishes we ping back
- try {
- const stream = Streams.ofChars("1/2/3/4/5/");
- blocks.parse(stream);
- parentPort.postMessage("finished"); // should never happen
- } catch (e) {
- parentPort.postMessage({ error: e.message });
- }
- `
-
- const worker = new Worker(workerSource, { eval: true })
-
- // Wrap the worker in a promise that rejects after 300 ms.
- const parsePromise: Promise = new Promise((_, reject) => {
- // βΆ Rejection timer
- const timer = setTimeout(() => {
- // kill the worker and reject with a custom error
- worker.terminate().then(() => reject(new Error('timeout')))
- }, 300)
-
- // β· Any message from the worker clears the timer and fails the test
- worker.once('message', (msg) => {
- clearTimeout(timer)
- // We expect a *timeout*, so any normal completion is a failure
- reject(
- new Error(
- typeof msg === 'string'
- ? `unexpected worker completion: ${msg}`
- : `worker error: ${msg.error}`,
- ),
- )
- })
-
- // βΈ Propagate worker runtime errors
- worker.once('error', (err) => {
- clearTimeout(timer)
- reject(err)
- })
- })
-
- // The assertion: the promise **must** reject with βtimeoutβ.
- await expect(parsePromise).rejects.toThrow('timeout')
- }, 1_000) // give the whole test 1 s β plenty beyond the 300 ms guard
-})
diff --git a/ingest/examples/opt-and-rep/rep.spec.ts b/ingest/examples/opt-and-rep/rep.spec.ts
index 063aef99..81509c0f 100644
--- a/ingest/examples/opt-and-rep/rep.spec.ts
+++ b/ingest/examples/opt-and-rep/rep.spec.ts
@@ -1,4 +1,4 @@
-import { C, N, Streams } from '@masala/parser'
+import { C, N, Stream } from '@masala/parser'
import { describe, it, expect } from 'vitest'
describe('rep parser', () => {
@@ -27,7 +27,7 @@ describe('rep parser', () => {
const n = N.number().then(C.char('/')).rep()
const string = '1/2/3/4/5/'
- const stream = Streams.ofChars(string)
+ const stream = Stream.ofChars(string)
const response = n.parse(stream)
const data = response.value
@@ -48,7 +48,7 @@ describe('rep parser', () => {
.rep()
const string = '1#2#3/4/5/'
- const stream = Streams.ofChars(string)
+ const stream = Stream.ofChars(string)
const response = n.parse(stream)
const data = response.value
@@ -62,7 +62,7 @@ describe('rep parser', () => {
const n = N.number().then(C.char('/')).rep().rep()
const string = '1/2/3/4/5/'
- const stream = Streams.ofChars(string)
+ const stream = Stream.ofChars(string)
const response = n.parse(stream)
const data = response.value
@@ -74,7 +74,7 @@ describe('rep parser', () => {
const n = N.digit().rep()
const string = '12345'
- const stream = Streams.ofChars(string)
+ const stream = Stream.ofChars(string)
const response = n.parse(stream)
const data = response.value
diff --git a/ingest/test/genlex/genlex-brainfuck.spec.js b/ingest/test/genlex/genlex-brainfuck.spec.js
index 08379bb2..bf4353b6 100644
--- a/ingest/test/genlex/genlex-brainfuck.spec.js
+++ b/ingest/test/genlex/genlex-brainfuck.spec.js
@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest'
import { GenLex } from '../../lib/genlex/genlex'
import { F, C } from '../../lib/core/index' // Added .js extension
-import Streams from '../../lib/stream'
+import Stream from '../../lib/stream'
function createParser() {
const genlex = new GenLex()
@@ -20,7 +20,7 @@ describe('GenLex Brainfuck Tokenizer Tests', () => {
let hW =
'++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.'
let parser = createParser()
- const response = parser.parse(Streams.ofChars(hW))
+ const response = parser.parse(Stream.ofChars(hW))
expect(response.isAccepted()).toBe(true)
// Check if all characters that are commands were tokenized
@@ -68,7 +68,7 @@ Pointer : ^
>++. And finally a newline from Cell #6`
let parser = createParser()
- const response = parser.parse(Streams.ofChars(hW))
+ const response = parser.parse(Stream.ofChars(hW))
expect(response.isAccepted()).toBe(true)
// The original test asserted offset 106. This is the count of actual Brainfuck command characters.
diff --git a/ingest/test/parsec/chars-bundle-regex.spec.js b/ingest/test/parsec/chars-bundle-regex.spec.js
index f3a58212..ddefb4ce 100644
--- a/ingest/test/parsec/chars-bundle-regex.spec.js
+++ b/ingest/test/parsec/chars-bundle-regex.spec.js
@@ -1,29 +1,29 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { N, C } from '../../lib/core/index'
describe('Chars Bundle Tests', () => {
it('accepts a character inside the range', () => {
- const parsing = C.inRegexRange('a-z').parse(Streams.ofChars('c'))
+ const parsing = C.inRegexRange('a-z').parse(Stream.ofChars('c'))
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('c')
expect(parsing.offset).toBe(1) // consumed one char
})
it('rejects a character outside the range', () => {
- const parsing = C.inRegexRange('a-z').parse(Streams.ofChars('Z'))
+ const parsing = C.inRegexRange('a-z').parse(Stream.ofChars('Z'))
expect(parsing.isAccepted()).toBe(false)
expect(parsing.offset).toBe(0) // cursor untouched on failure
})
it('accepts a digit with /[0-9]/', () => {
- const parsing = C.inRegexRange('0-9').parse(Streams.ofChars('7'))
+ const parsing = C.inRegexRange('0-9').parse(Stream.ofChars('7'))
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('7')
})
it('rejects a letter with /[0-9]/', () => {
- const parsing = C.inRegexRange('0-9').parse(Streams.ofChars('a'))
+ const parsing = C.inRegexRange('0-9').parse(Stream.ofChars('a'))
expect(parsing.isAccepted()).toBe(false)
})
})
@@ -32,21 +32,21 @@ describe('C.inRegexRange β identifier βfirst charβ rule', () => {
const identStart = C.inRegexRange('a-zA-Z_')
it('accepts a letter', () => {
- expect(identStart.parse(Streams.ofChars('B')).isAccepted()).toBe(true)
+ expect(identStart.parse(Stream.ofChars('B')).isAccepted()).toBe(true)
})
it('accepts an underscore', () => {
- expect(identStart.parse(Streams.ofChars('_')).isAccepted()).toBe(true)
+ expect(identStart.parse(Stream.ofChars('_')).isAccepted()).toBe(true)
})
it('rejects a digit', () => {
- expect(identStart.parse(Streams.ofChars('3')).isAccepted()).toBe(false)
+ expect(identStart.parse(Stream.ofChars('3')).isAccepted()).toBe(false)
})
})
describe('C.inRegexRange in a more complex stream', () => {
it('accepts the string', () => {
- const stream = Streams.ofChars('0a1')
+ const stream = Stream.ofChars('0a1')
const parser = N.digit().then(C.inRegexRange('a-c')).then(N.digit())
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(true)
@@ -54,7 +54,7 @@ describe('C.inRegexRange in a more complex stream', () => {
})
it('reject the range', () => {
- const stream = Streams.ofChars('0d1')
+ const stream = Stream.ofChars('0d1')
const parser = N.digit().then(C.inRegexRange('a-c')).then(N.digit())
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(false)
diff --git a/ingest/test/parsec/chars-bundle-test.spec.js b/ingest/test/parsec/chars-bundle-test.spec.js
index 8f039c7e..c4e671f9 100644
--- a/ingest/test/parsec/chars-bundle-test.spec.js
+++ b/ingest/test/parsec/chars-bundle-test.spec.js
@@ -1,16 +1,16 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { F, C } from '../../lib/core/index'
describe('Chars Bundle Tests', () => {
it('expect (char) to be accepted', () => {
- expect(C.char('a').parse(Streams.ofChars('a'), 0).isAccepted()).toBe(
+ expect(C.char('a').parse(Stream.ofChars('a'), 0).isAccepted()).toBe(
true,
)
})
it('expect (char) to be rejected', () => {
- expect(C.char('a').parse(Streams.ofChars('b'), 0).isAccepted()).toBe(
+ expect(C.char('a').parse(Stream.ofChars('b'), 0).isAccepted()).toBe(
false,
)
})
@@ -20,13 +20,13 @@ describe('Chars Bundle Tests', () => {
})
it('expect (notChar) to be accepted', () => {
- expect(C.notChar('a').parse(Streams.ofChars('b'), 0).isAccepted()).toBe(
+ expect(C.notChar('a').parse(Stream.ofChars('b'), 0).isAccepted()).toBe(
true,
)
})
it('expect (notChar) to be rejected', () => {
- expect(C.notChar('a').parse(Streams.ofChars('a'), 0).isAccepted()).toBe(
+ expect(C.notChar('a').parse(Stream.ofChars('a'), 0).isAccepted()).toBe(
false,
)
})
@@ -37,101 +37,95 @@ describe('Chars Bundle Tests', () => {
it('expect (charNotIn) to be accepted', () => {
expect(
- C.charNotIn('a').parse(Streams.ofChars('b'), 0).isAccepted(),
+ C.charNotIn('a').parse(Stream.ofChars('b'), 0).isAccepted(),
).toBe(true)
})
it('expect (charNotIn) to be rejected', () => {
expect(
- C.charNotIn('a').parse(Streams.ofChars('a'), 0).isAccepted(),
+ C.charNotIn('a').parse(Stream.ofChars('a'), 0).isAccepted(),
).toBe(false)
})
it('expect (charIn) to be accepted', () => {
- expect(C.charIn('a').parse(Streams.ofChars('a'), 0).isAccepted()).toBe(
+ expect(C.charIn('a').parse(Stream.ofChars('a'), 0).isAccepted()).toBe(
true,
)
})
it('expect (charIn) to be rejected', () => {
- expect(C.charIn('a').parse(Streams.ofChars('b'), 0).isAccepted()).toBe(
+ expect(C.charIn('a').parse(Stream.ofChars('b'), 0).isAccepted()).toBe(
false,
)
})
it('expect (lowerCase) to be accepted', () => {
- expect(C.lowerCase().parse(Streams.ofChars('a'), 0).isAccepted()).toBe(
+ expect(C.lowerCase().parse(Stream.ofChars('a'), 0).isAccepted()).toBe(
true,
)
})
it('expect (lowerCase) to be rejected', () => {
- expect(C.lowerCase().parse(Streams.ofChars('A'), 0).isAccepted()).toBe(
+ expect(C.lowerCase().parse(Stream.ofChars('A'), 0).isAccepted()).toBe(
false,
)
})
it('expect (upperCase) to be accepted', () => {
- expect(C.upperCase().parse(Streams.ofChars('A'), 0).isAccepted()).toBe(
+ expect(C.upperCase().parse(Stream.ofChars('A'), 0).isAccepted()).toBe(
true,
)
})
it('expect (upperCase) to be rejected', () => {
- expect(C.upperCase().parse(Streams.ofChars('z'), 0).isAccepted()).toBe(
+ expect(C.upperCase().parse(Stream.ofChars('z'), 0).isAccepted()).toBe(
false,
)
})
it('expect upper (letter) to be accepted', () => {
- expect(C.letter().parse(Streams.ofChars('A'), 0).isAccepted()).toBe(
- true,
- )
+ expect(C.letter().parse(Stream.ofChars('A'), 0).isAccepted()).toBe(true)
})
it('expect lower (letter) to be accepted', () => {
- expect(C.letter().parse(Streams.ofChars('z'), 0).isAccepted()).toBe(
- true,
- )
+ expect(C.letter().parse(Stream.ofChars('z'), 0).isAccepted()).toBe(true)
})
it('expect space (letter) to be rejected', () => {
- expect(C.letter().parse(Streams.ofChars(' '), 0).isAccepted()).toBe(
+ expect(C.letter().parse(Stream.ofChars(' '), 0).isAccepted()).toBe(
false,
)
})
it('expect non (letter) to be rejected', () => {
- expect(C.letter().parse(Streams.ofChars('0'), 0).isAccepted()).toBe(
+ expect(C.letter().parse(Stream.ofChars('0'), 0).isAccepted()).toBe(
false,
)
})
it('expect occidental letter to be accepted', () => {
- expect(C.letter().parse(Streams.ofChars('a'), 0).isAccepted()).toBe(
- true,
- )
- expect(C.letterAs().parse(Streams.ofChars('a'), 0).isAccepted()).toBe(
+ expect(C.letter().parse(Stream.ofChars('a'), 0).isAccepted()).toBe(true)
+ expect(C.letterAs().parse(Stream.ofChars('a'), 0).isAccepted()).toBe(
true,
)
expect(
C.letterAs(C.OCCIDENTAL_LETTER)
- .parse(Streams.ofChars('a'))
+ .parse(Stream.ofChars('a'))
.isAccepted(),
).toBe(true)
expect(
C.letterAs(C.OCCIDENTAL_LETTER)
- .parse(Streams.ofChars('Γ©'))
+ .parse(Stream.ofChars('Γ©'))
.isAccepted(),
).toBe(true)
expect(
C.letterAs(C.OCCIDENTAL_LETTER)
- .parse(Streams.ofChars('Π'))
+ .parse(Stream.ofChars('Π'))
.isAccepted(),
).toBe(false)
expect(
C.letterAs(C.OCCIDENTAL_LETTER)
- .parse(Streams.ofChars('Γ·'))
+ .parse(Stream.ofChars('Γ·'))
.isAccepted(),
).toBe(false)
})
@@ -140,30 +134,30 @@ describe('Chars Bundle Tests', () => {
expect(
C.letters()
.then(F.eos())
- .parse(Streams.ofChars('aΓ©Γ'), 0)
+ .parse(Stream.ofChars('aΓ©Γ'), 0)
.isAccepted(),
).toBe(true)
expect(
C.lettersAs()
.then(F.eos())
- .parse(Streams.ofChars('aΓ©Γ'), 0)
+ .parse(Stream.ofChars('aΓ©Γ'), 0)
.isAccepted(),
).toBe(true)
expect(
C.lettersAs(C.OCCIDENTAL_LETTER)
- .parse(Streams.ofChars('a'))
+ .parse(Stream.ofChars('a'))
.isAccepted(),
).toBe(true)
expect(
C.lettersAs(C.OCCIDENTAL_LETTER)
.then(F.eos())
- .parse(Streams.ofChars('Γ©A'))
+ .parse(Stream.ofChars('Γ©A'))
.isAccepted(),
).toBe(true)
expect(
C.lettersAs(C.OCCIDENTAL_LETTER)
.then(F.eos())
- .parse(Streams.ofChars('ΠAs'))
+ .parse(Stream.ofChars('ΠAs'))
.isAccepted(),
).toBe(false)
})
@@ -171,14 +165,14 @@ describe('Chars Bundle Tests', () => {
it('expect ascii letter to be accepted', () => {
expect(
C.letterAs(C.ASCII_LETTER)
- .parse(Streams.ofChars('a'), 0)
+ .parse(Stream.ofChars('a'), 0)
.isAccepted(),
).toBe(true)
expect(
- C.letterAs(C.ASCII_LETTER).parse(Streams.ofChars('Γ©')).isAccepted(),
+ C.letterAs(C.ASCII_LETTER).parse(Stream.ofChars('Γ©')).isAccepted(),
).toBe(false)
expect(
- C.letterAs(C.ASCII_LETTER).parse(Streams.ofChars('Π')).isAccepted(),
+ C.letterAs(C.ASCII_LETTER).parse(Stream.ofChars('Π')).isAccepted(),
).toBe(false)
})
@@ -186,23 +180,23 @@ describe('Chars Bundle Tests', () => {
expect(
C.lettersAs(C.ASCII_LETTER)
.then(F.eos())
- .parse(Streams.ofChars('a'))
+ .parse(Stream.ofChars('a'))
.isAccepted(),
).toBe(true)
})
it('expect utf8 letter to be accepted', () => {
expect(
- C.letterAs(C.UTF8_LETTER).parse(Streams.ofChars('a')).isAccepted(),
+ C.letterAs(C.UTF8_LETTER).parse(Stream.ofChars('a')).isAccepted(),
).toBe(true)
expect(
- C.letterAs(C.UTF8_LETTER).parse(Streams.ofChars('Γ©')).isAccepted(),
+ C.letterAs(C.UTF8_LETTER).parse(Stream.ofChars('Γ©')).isAccepted(),
).toBe(true)
expect(
- C.letterAs(C.UTF8_LETTER).parse(Streams.ofChars('Π')).isAccepted(),
+ C.letterAs(C.UTF8_LETTER).parse(Stream.ofChars('Π')).isAccepted(),
).toBe(true)
expect(
- C.letterAs(C.UTF8_LETTER).parse(Streams.ofChars('Γ·')).isAccepted(),
+ C.letterAs(C.UTF8_LETTER).parse(Stream.ofChars('Γ·')).isAccepted(),
).toBe(false)
})
@@ -210,31 +204,31 @@ describe('Chars Bundle Tests', () => {
expect(
C.lettersAs(C.UTF8_LETTER)
.then(F.eos())
- .parse(Streams.ofChars('a'))
+ .parse(Stream.ofChars('a'))
.isAccepted(),
).toBe(true)
expect(
C.lettersAs(C.UTF8_LETTER)
.then(F.eos())
- .parse(Streams.ofChars('Γ©A'))
+ .parse(Stream.ofChars('Γ©A'))
.isAccepted(),
).toBe(true)
expect(
C.lettersAs(C.UTF8_LETTER)
.then(F.eos())
- .parse(Streams.ofChars('ΠAs'))
+ .parse(Stream.ofChars('ΠAs'))
.isAccepted(),
).toBe(true)
expect(
C.letterAs(C.UTF8_LETTER)
.then(F.eos())
- .parse(Streams.ofChars('ΠΓ·As'))
+ .parse(Stream.ofChars('ΠΓ·As'))
.isAccepted(),
).toBe(false)
})
it('expect unknown letters to be rejected', () => {
- const line = Streams.ofChars('a')
+ const line = Stream.ofChars('a')
expect(() => {
const combinator = C.lettersAs(Symbol('UNKNOWN')).then(F.eos())
combinator.parse(line)
@@ -245,7 +239,7 @@ describe('Chars Bundle Tests', () => {
const parsing = C.letters()
.thenLeft(F.eos())
.single()
- .parse(Streams.ofChars('someLetters'), 0)
+ .parse(Stream.ofChars('someLetters'), 0)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('someLetters')
})
@@ -253,7 +247,7 @@ describe('Chars Bundle Tests', () => {
it('expect (letters) with space to be rejected', () => {
const parsing = C.letters()
.then(F.eos())
- .parse(Streams.ofChars('some Letters'), 0)
+ .parse(Stream.ofChars('some Letters'), 0)
expect(parsing.isAccepted()).toBe(false)
expect(parsing.offset).toBe(4)
})
@@ -261,7 +255,7 @@ describe('Chars Bundle Tests', () => {
it('expect (letters) with number to be rejected', () => {
const parsing = C.letters()
.then(F.eos())
- .parse(Streams.ofChars('some2Letters'), 0)
+ .parse(Stream.ofChars('some2Letters'), 0)
expect(parsing.isAccepted()).toBe(false)
})
@@ -269,24 +263,24 @@ describe('Chars Bundle Tests', () => {
const parsing = C.letters()
.thenLeft(F.eos())
.single()
- .parse(Streams.ofChars('someLetters'), 0)
+ .parse(Stream.ofChars('someLetters'), 0)
expect(parsing.value).toBe('someLetters')
})
it('expect (string) to be accepted', () => {
expect(
- C.string('Hello').parse(Streams.ofChars('Hello'), 0).isAccepted(),
+ C.string('Hello').parse(Stream.ofChars('Hello'), 0).isAccepted(),
).toBe(true)
})
it('expect (string) to be rejected', () => {
expect(
- C.string('hello').parse(Streams.ofChars('hell'), 0).isAccepted(),
+ C.string('hello').parse(Stream.ofChars('hell'), 0).isAccepted(),
).toBe(false)
})
it('test stringIn', () => {
- let line = Streams.ofChars('James Bond')
+ let line = Stream.ofChars('James Bond')
const combinator = C.stringIn(['The', 'James', 'Bond', 'series'])
const value = combinator.parse(line).value
expect(typeof value).toBe('string')
@@ -294,7 +288,7 @@ describe('Chars Bundle Tests', () => {
})
it('test stringIn Similar', () => {
- let line = Streams.ofChars('Jack James Jane')
+ let line = Stream.ofChars('Jack James Jane')
const combinator = C.stringIn(['Jamie', 'Jacko', 'Jack'])
const parsing = combinator.parse(line)
const value = parsing.value
@@ -304,7 +298,7 @@ describe('Chars Bundle Tests', () => {
})
it('test stringIn one string sidecase', () => {
- let line = Streams.ofChars('James')
+ let line = Stream.ofChars('James')
const combinator = C.stringIn(['James'])
const value = combinator.parse(line).value
expect(typeof value).toBe('string')
@@ -312,14 +306,14 @@ describe('Chars Bundle Tests', () => {
})
it('test stringIn empty sidecase', () => {
- let line = Streams.ofChars('James')
+ let line = Stream.ofChars('James')
const combinator = C.stringIn([]).then(F.eos())
const parsing = combinator.parse(line)
expect(parsing.isAccepted()).toBe(false)
})
it('test stringIn empty accept nothing sidecase', () => {
- let line = Streams.ofChars('')
+ let line = Stream.ofChars('')
const combinator = C.stringIn([]).then(F.eos())
const parsing = combinator.parse(line)
expect(parsing.isAccepted()).toBe(true)
@@ -327,75 +321,72 @@ describe('Chars Bundle Tests', () => {
it('expect (notString) to be accepted', () => {
expect(
- C.notString('**').parse(Streams.ofChars('hello'), 0).isAccepted(),
+ C.notString('**').parse(Stream.ofChars('hello'), 0).isAccepted(),
).toBe(true)
})
it('expect (notString) to be h', () => {
- expect(C.notString('**').parse(Streams.ofChars('hello'), 0).value).toBe(
+ expect(C.notString('**').parse(Stream.ofChars('hello'), 0).value).toBe(
'h',
)
})
it('expect (notString) to be rejected', () => {
expect(
- C.notString('**').parse(Streams.ofChars('**hello'), 0).isAccepted(),
+ C.notString('**').parse(Stream.ofChars('**hello'), 0).isAccepted(),
).toBe(false)
})
it('expect accent to be accepted', () => {
- expect(C.utf8Letter().parse(Streams.ofChars('Γ©'), 0).isAccepted()).toBe(
+ expect(C.utf8Letter().parse(Stream.ofChars('Γ©'), 0).isAccepted()).toBe(
true,
)
})
it('expect cyriliq to be accepted', () => {
- expect(C.utf8Letter().parse(Streams.ofChars('Π'), 0).isAccepted()).toBe(
+ expect(C.utf8Letter().parse(Stream.ofChars('Π'), 0).isAccepted()).toBe(
true,
)
- expect(C.utf8Letter().parse(Streams.ofChars('Π±'), 0).isAccepted()).toBe(
+ expect(C.utf8Letter().parse(Stream.ofChars('Π±'), 0).isAccepted()).toBe(
true,
)
})
it('expect dash to be rejected', () => {
- expect(C.utf8Letter().parse(Streams.ofChars('-'), 0).isAccepted()).toBe(
+ expect(C.utf8Letter().parse(Stream.ofChars('-'), 0).isAccepted()).toBe(
false,
)
})
it('expect "nothing" to be rejected', () => {
- expect(C.utf8Letter().parse(Streams.ofChars(''), 0).isAccepted()).toBe(
+ expect(C.utf8Letter().parse(Stream.ofChars(''), 0).isAccepted()).toBe(
false,
)
})
it('expect emoji to be accepted', () => {
expect(
- C.emoji().then(F.eos()).parse(Streams.ofChars('Π±'), 0).isAccepted(),
+ C.emoji().then(F.eos()).parse(Stream.ofChars('Π±'), 0).isAccepted(),
).toBe(false)
expect(
- C.emoji().then(F.eos()).parse(Streams.ofChars('a'), 0).isAccepted(),
+ C.emoji().then(F.eos()).parse(Stream.ofChars('a'), 0).isAccepted(),
).toBe(false)
expect(
C.emoji()
.then(F.eos())
- .parse(Streams.ofChars('π΅π΅βοΈ'), 0)
+ .parse(Stream.ofChars('π΅π΅βοΈ'), 0)
.isAccepted(),
).toBe(true)
expect(
- C.emoji()
- .then(F.eos())
- .parse(Streams.ofChars('βοΈ'), 0)
- .isAccepted(),
+ C.emoji().then(F.eos()).parse(Stream.ofChars('βοΈ'), 0).isAccepted(),
).toBe(true)
expect(
- C.emoji().then(F.eos()).parse(Streams.ofChars('π₯ͺ')).isAccepted(),
+ C.emoji().then(F.eos()).parse(Stream.ofChars('π₯ͺ')).isAccepted(),
).toBe(true)
})
it('expect subString to works', () => {
- let stream = Streams.ofChars('James Bond')
+ let stream = Stream.ofChars('James Bond')
let parser = C.subString(6).then(C.string('Bond'))
const response = parser.parse(stream)
expect(response.value.array()).toEqual(['James ', 'Bond'])
diff --git a/ingest/test/parsec/f-layer-test.spec.js b/ingest/test/parsec/f-layer-test.spec.js
index 43fc13a0..ef044402 100644
--- a/ingest/test/parsec/f-layer-test.spec.js
+++ b/ingest/test/parsec/f-layer-test.spec.js
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { F, C } from '../../lib/core/index'
describe('F Layer Tests', () => {
@@ -10,12 +10,12 @@ describe('F Layer Tests', () => {
const layer = F.layer(parser)
- let response = layer.parse(Streams.ofChars(successInput))
+ let response = layer.parse(Stream.ofChars(successInput))
expect(response.isAccepted()).toBe(true)
expect(response.offset).toBe(0)
- response = layer.parse(Streams.ofChars(failInput))
+ response = layer.parse(Stream.ofChars(failInput))
expect(response.isAccepted()).toBe(false)
expect(response.offset).toBe(0)
@@ -30,7 +30,7 @@ describe('F Layer Tests', () => {
const second = C.string('aa').thenEos()
const successInput = 'aa'
const layer = F.layer(first).and(second).and(second).array()
- let response = layer.parse(Streams.ofChars(successInput))
+ let response = layer.parse(Stream.ofChars(successInput))
expect(response.isAccepted()).toBe(true)
expect(response.value).toEqual([2, 'aa', 'aa'])
@@ -51,7 +51,7 @@ describe('F Layer Tests', () => {
const third = C.string('aa').thenEos()
const input = 'aa'
const layer = F.layer(first).and(second).and(third).array()
- let response = layer.parse(Streams.ofChars(input))
+ let response = layer.parse(Stream.ofChars(input))
expect(response.isAccepted()).toBe(true)
expect(response.value).toEqual([2, 'a-a', 'aa'])
@@ -67,7 +67,7 @@ describe('F Layer Tests', () => {
const second = C.string('aaFAIL').thenEos()
const successInput = 'aa'
const layer = F.layer(first).and(second).array()
- let response = layer.parse(Streams.ofChars(successInput))
+ let response = layer.parse(Stream.ofChars(successInput))
expect(response.isAccepted()).toBe(false)
expect(response.offset).toBe(0)
@@ -82,7 +82,7 @@ describe('F Layer Tests', () => {
const second = C.string('aaSUCCESS').thenEos()
const successInput = 'aaSUCCESS'
const layer = F.layer(first).and(second)
- let response = layer.parse(Streams.ofChars(successInput))
+ let response = layer.parse(Stream.ofChars(successInput))
expect(response.isAccepted()).toBe(false)
expect(response.offset).toBe(2)
@@ -103,7 +103,7 @@ describe('F Layer Tests', () => {
})
const successInput = 'aaSUCCESS'
const layer = F.layer(first).and(second)
- let response = layer.parse(Streams.ofChars(successInput))
+ let response = layer.parse(Stream.ofChars(successInput))
expect(response.isAccepted()).toBe(false)
expect(response.offset).toBe(2)
diff --git a/ingest/test/parsec/flow-bundle-test.spec.js b/ingest/test/parsec/flow-bundle-test.spec.js
index 4e82f5d7..f2ec528a 100644
--- a/ingest/test/parsec/flow-bundle-test.spec.js
+++ b/ingest/test/parsec/flow-bundle-test.spec.js
@@ -1,10 +1,10 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { F, C } from '../../lib/core/index'
import { GenLex } from '../../lib'
function testParser(parser, string) {
- let stream = Streams.ofChars(string)
+ let stream = Stream.ofChars(string)
let parsing = parser.parse(stream)
return parsing
}
@@ -13,7 +13,7 @@ describe('Flow Bundle Tests', () => {
it('subStream is ok on string stream', () => {
const text = 'Hello World'
const parser = F.subStream(6).then(C.string('World'))
- const response = parser.parse(Streams.ofChars(text))
+ const response = parser.parse(Stream.ofChars(text))
expect(response.isAccepted()).toBe(true)
expect(response.value.size()).toBe(7)
@@ -26,7 +26,7 @@ describe('Flow Bundle Tests', () => {
const grammar = F.subStream(4).drop().then(F.any().rep())
const parser = genlex.use(grammar)
const text = '++++ and then >>'
- const response = parser.parse(Streams.ofChars(text))
+ const response = parser.parse(Stream.ofChars(text))
expect(response.isAccepted()).toBe(true)
expect(response.value.size()).toBe(2)
@@ -38,12 +38,12 @@ describe('Flow Bundle Tests', () => {
const eol = C.char('\n')
const parser = F.not(eol).rep()
- let response = parser.parse(Streams.ofChars(line))
+ let response = parser.parse(Stream.ofChars(line))
expect(response.isAccepted()).toBe(true)
expect(response.offset).toBe(text.length)
const withParser = F.not(eol).rep().then(eol)
- response = withParser.parse(Streams.ofChars(line))
+ response = withParser.parse(Stream.ofChars(line))
expect(response.isAccepted()).toBe(true)
expect(response.offset).toBe(line.length)
})
@@ -79,7 +79,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntilFast string', () => {
- const line = Streams.ofChars('soXYZso')
+ const line = Stream.ofChars('soXYZso')
const combinator = F.moveUntil('XYZ')
const parser = combinator.parse(line)
expect(parser.value).toBe('so')
@@ -87,7 +87,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntilFast string with include', () => {
- const line = Streams.ofChars('soXYZso')
+ const line = Stream.ofChars('soXYZso')
const combinator = F.moveUntil('XYZ', true)
const parser = combinator.parse(line)
expect(parser.value).toBe('soXYZ')
@@ -96,7 +96,7 @@ describe('Flow Bundle Tests', () => {
it('test moveUntilFast string with continuation', () => {
const document = 'start-detect-XYZ-continues'
- const line = Streams.ofChars(document)
+ const line = Stream.ofChars(document)
const start = C.string('start-')
const combinator = start
.drop()
@@ -110,7 +110,7 @@ describe('Flow Bundle Tests', () => {
it('test moveUntilFast array of string with continuation', () => {
const document = 'start-detect-XYZ-continues'
- const line = Streams.ofChars(document)
+ const line = Stream.ofChars(document)
const start = C.string('start-')
const combinator = start
.drop()
@@ -124,7 +124,7 @@ describe('Flow Bundle Tests', () => {
it('test moveUntilFast array of string with include', () => {
const document = 'start-detect-XYZ-continues'
- const line = Streams.ofChars(document)
+ const line = Stream.ofChars(document)
const start = C.string('start-')
const combinator = start
.drop()
@@ -139,7 +139,7 @@ describe('Flow Bundle Tests', () => {
it('test moveUntilFast string fails', () => {
const document = 'start-detect-XYZ-continues'
- const line = Streams.ofChars(document)
+ const line = Stream.ofChars(document)
const start = C.string('start-')
const combinator = start
.drop()
@@ -151,7 +151,7 @@ describe('Flow Bundle Tests', () => {
it('test moveUntilFast array of string fails', () => {
const document = 'start-detect-XYZ-continues'
- const line = Streams.ofChars(document)
+ const line = Stream.ofChars(document)
const start = C.string('start-')
const combinator = start
.drop()
@@ -163,7 +163,7 @@ describe('Flow Bundle Tests', () => {
it('test moveUntilFast fails if array stream', () => {
const document = ['More', 'XYZ']
- const line = Streams.ofArrays(document)
+ const line = Stream.ofArrays(document)
const combinator = F.moveUntil(['ABC', 'ZE', 'XYZ'])
expect(() => combinator.parse(line)).toThrow(
'Input source must be a String',
@@ -172,7 +172,7 @@ describe('Flow Bundle Tests', () => {
it('test moveUntilFastString fails if array stream', () => {
const document = ['More', 'XYZ']
- const line = Streams.ofArrays(document)
+ const line = Stream.ofArrays(document)
const combinator = F.moveUntil('XYZ')
expect(() => combinator.parse(line)).toThrow(
'Input source must be a String',
@@ -180,7 +180,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntil', () => {
- const line = Streams.ofChars('I write until James appears')
+ const line = Stream.ofChars('I write until James appears')
const combinator = F.moveUntil(C.string('James'))
.then(F.any().drop())
.single()
@@ -189,7 +189,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntil parser, with include', () => {
- const line = Streams.ofChars('I write until James appears')
+ const line = Stream.ofChars('I write until James appears')
const combinator = F.moveUntil(C.string('James'), true)
.then(F.any().rep().drop())
.single()
@@ -201,7 +201,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntil parser, with include and structure', () => {
- const line = Streams.ofChars('I write until James appears')
+ const line = Stream.ofChars('I write until James appears')
const combinator = F.moveUntil(
C.string('James').map((james) => ({
structure: james,
@@ -218,7 +218,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntil parser, with eos, not including', () => {
- const line = Streams.ofChars('I write until the end')
+ const line = Stream.ofChars('I write until the end')
const combinator = F.moveUntil(C.string('end'), false)
const parsing = combinator.parse(line)
const value = parsing.value
@@ -228,7 +228,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntil parser, with eos, including', () => {
- const line = Streams.ofChars('I write until James appears')
+ const line = Stream.ofChars('I write until James appears')
const combinator = F.moveUntil(C.string('appears'), true)
const parsing = combinator.parse(line)
const value = parsing.value
@@ -238,7 +238,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntil Not found', () => {
- const line = Streams.ofChars('I write until James appears')
+ const line = Stream.ofChars('I write until James appears')
const combinator = F.moveUntil(C.string('Indiana'))
.then(C.string('I'))
.then(F.any().drop())
@@ -247,7 +247,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntil found with failing parser', () => {
- const line = Streams.ofChars('I write until James Bond appears')
+ const line = Stream.ofChars('I write until James Bond appears')
const combinator = F.moveUntil(C.string('James')).then(
F.dropTo(F.eos()),
)
@@ -256,7 +256,7 @@ describe('Flow Bundle Tests', () => {
})
it('test dropTo with string', () => {
- const line = Streams.ofChars('I write until James Bond appears')
+ const line = Stream.ofChars('I write until James Bond appears')
const combinator = F.dropTo('James')
.then(C.string(' Bond appears'))
.then(F.eos())
@@ -265,7 +265,7 @@ describe('Flow Bundle Tests', () => {
})
it('test dropTo with string fail', () => {
- const line = Streams.ofChars('I write until James Bond appears')
+ const line = Stream.ofChars('I write until James Bond appears')
const combinator = F.dropTo('James')
.then(C.string(' Bond appears'))
.then(F.eos())
@@ -274,7 +274,7 @@ describe('Flow Bundle Tests', () => {
})
it('test dropTo with parser', () => {
- const line = Streams.ofChars('I write until James Bond appears')
+ const line = Stream.ofChars('I write until James Bond appears')
const combinator = F.dropTo(C.string('James'))
.then(C.string(' Bond appears'))
.then(F.eos())
@@ -283,7 +283,7 @@ describe('Flow Bundle Tests', () => {
})
it('test moveUntil found with more parsers', () => {
- const line = Streams.ofChars('I write until James Bond appears')
+ const line = Stream.ofChars('I write until James Bond appears')
const combinator = F.moveUntil(C.string('James'))
.then(F.dropTo('appears'))
.then(F.eos().drop())
@@ -311,7 +311,7 @@ describe('Flow Bundle Tests', () => {
}
}
- const line = Streams.ofChars('ababa')
+ const line = Stream.ofChars('ababa')
const combinator = new SomeLazyParser('a').first().then(F.eos().drop())
const value = combinator.parse(line).value
expect(value.join('')).toBe('ababa')
diff --git a/ingest/test/parsec/flow-move-until.spec.js b/ingest/test/parsec/flow-move-until.spec.js
index f62fa141..24f622af 100644
--- a/ingest/test/parsec/flow-move-until.spec.js
+++ b/ingest/test/parsec/flow-move-until.spec.js
@@ -1,11 +1,11 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { F, C } from '../../lib/core/index'
describe('moveUntil do not return a TupleParser', () => {
it('test moveUntil returning a string when stopping at a string', () => {
const document = 'aaXYZb'
- const line = Streams.ofChars(document)
+ const line = Stream.ofChars(document)
const combinator = F.moveUntil('XYZ')
const parser = combinator.parse(line)
expect(parser.value).toBe('aa')
@@ -13,7 +13,7 @@ describe('moveUntil do not return a TupleParser', () => {
it('test moveUntil returning a string when stopping at parser', () => {
const document = 'aaXYZb'
- const line = Streams.ofChars(document)
+ const line = Stream.ofChars(document)
const combinator = F.moveUntil(C.string('XYZ'))
const parser = combinator.parse(line)
expect(parser.value).toBe('aa')
diff --git a/ingest/test/parsec/flow-regex-flags.spec.js b/ingest/test/parsec/flow-regex-flags.spec.js
index d39158a0..ab5169ab 100644
--- a/ingest/test/parsec/flow-regex-flags.spec.js
+++ b/ingest/test/parsec/flow-regex-flags.spec.js
@@ -1,19 +1,19 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { F } from '../../lib/core/index'
describe('F.regex β behaviour of individual RegExp flags', () => {
/* βββββββββββββββββββββββββββββ i : ignore-case βββββββββββββββββββββββββββ */
it('i flag β matches despite case difference / no-i flag β fails', () => {
// with i
- let stream = Streams.ofChars('ABC')
+ let stream = Stream.ofChars('ABC')
let parsing = F.regex(/abc/i).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('ABC')
expect(parsing.offset).toBe(3)
// without i
- stream = Streams.ofChars('ABC')
+ stream = Stream.ofChars('ABC')
parsing = F.regex(/abc/).parse(stream)
expect(parsing.isAccepted()).toBe(false)
expect(parsing.offset).toBe(0)
@@ -22,14 +22,14 @@ describe('F.regex β behaviour of individual RegExp flags', () => {
/* βββββββββββββββββββββββββββββ s : dotAll ββββββββββββββββββββββββββββββββ */
it('s flag β dot matches newline / no-s flag β fails', () => {
// with s
- let stream = Streams.ofChars('a\nb')
+ let stream = Stream.ofChars('a\nb')
let parsing = F.regex(/a.b/s).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('a\nb')
expect(parsing.offset).toBe(3)
// without s
- stream = Streams.ofChars('a\nb')
+ stream = Stream.ofChars('a\nb')
parsing = F.regex(/a.b/).parse(stream)
expect(parsing.isAccepted()).toBe(false)
})
@@ -39,14 +39,14 @@ describe('F.regex β behaviour of individual RegExp flags', () => {
const src = 'foo\nbar' // 'bar' starts at offset 4
// with m
- let stream = Streams.ofChars(src)
+ let stream = Stream.ofChars(src)
let parsing = F.regex(/^bar$/m).parse(stream, 4)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('bar')
expect(parsing.offset).toBe(7) // 4 + 3
// without m
- stream = Streams.ofChars(src)
+ stream = Stream.ofChars(src)
parsing = F.regex(/^bar$/).parse(stream, 4)
expect(parsing.isAccepted()).toBe(false)
})
@@ -56,14 +56,14 @@ describe('F.regex β behaviour of individual RegExp flags', () => {
const smile = 'π' // U+1F600 (surrogate pair)
// with u
- let stream = Streams.ofChars(smile)
+ let stream = Stream.ofChars(smile)
let parsing = F.regex(/\u{1F600}/u).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe(smile)
expect(parsing.offset).toBe(smile.length) // 2 code units in UTF-16
// without u
- stream = Streams.ofChars(smile)
+ stream = Stream.ofChars(smile)
parsing = F.regex(/\u{1F600}/).parse(stream)
expect(parsing.isAccepted()).toBe(false)
})
@@ -72,14 +72,14 @@ describe('F.regex β behaviour of individual RegExp flags', () => {
const stickyB = /b/y
// cursor on the b β accepted
- let stream = Streams.ofChars('ab')
+ let stream = Stream.ofChars('ab')
let parsing = F.regex(stickyB).parse(stream, 1) // start at index 1
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('b')
expect(parsing.offset).toBe(2) // consumed one char
// cursor before the b β rejected (sticky prevents look-ahead)
- stream = Streams.ofChars('ab')
+ stream = Stream.ofChars('ab')
parsing = F.regex(stickyB).parse(stream, 0) // start at index 0
expect(parsing.isAccepted()).toBe(false)
expect(parsing.offset).toBe(0) // untouched on failure
diff --git a/ingest/test/parsec/flow-regex.spec.js b/ingest/test/parsec/flow-regex.spec.js
index 1a12cd3a..b779e2e0 100644
--- a/ingest/test/parsec/flow-regex.spec.js
+++ b/ingest/test/parsec/flow-regex.spec.js
@@ -1,10 +1,10 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { F, C } from '../../lib/core/index'
describe('Chars Bundle Tests', () => {
it('accepts a single character', () => {
- const stream = Streams.ofChars('a')
+ const stream = Stream.ofChars('a')
const parsing = F.regex(/[a-z]/).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('a')
@@ -12,7 +12,7 @@ describe('Chars Bundle Tests', () => {
})
it('consumes when accepting a single character', () => {
- const stream = Streams.ofChars('aa')
+ const stream = Stream.ofChars('aa')
const parsing = F.regex(/[a-z]/).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('a')
@@ -20,7 +20,7 @@ describe('Chars Bundle Tests', () => {
})
it('do not consumes when rejecting a single character', () => {
- const stream = Streams.ofChars('0a')
+ const stream = Stream.ofChars('0a')
const parsing = F.regex(/[a-z]/).parse(stream)
expect(parsing.isAccepted()).toBe(false)
expect(parsing.value).toBeUndefined()
@@ -28,7 +28,7 @@ describe('Chars Bundle Tests', () => {
})
it('accepts multiple characters', () => {
- const stream = Streams.ofChars('abc')
+ const stream = Stream.ofChars('abc')
const parsing = F.regex(/[a-z]+/).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('abc')
@@ -36,7 +36,7 @@ describe('Chars Bundle Tests', () => {
})
it('moves as long as possible eating multiple characters', () => {
- const stream = Streams.ofChars('abc0')
+ const stream = Stream.ofChars('abc0')
const parsing = F.regex(/[a-z]+/).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('abc')
@@ -44,7 +44,7 @@ describe('Chars Bundle Tests', () => {
})
it('star is accepted even rejects when no characters match', () => {
- const stream = Streams.ofChars('0')
+ const stream = Stream.ofChars('0')
const parsing = F.regex(/[a-z]*/).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).not.toBeUndefined() // empty but accepted!
@@ -53,7 +53,7 @@ describe('Chars Bundle Tests', () => {
})
it('accept a identifier building', () => {
- const stream = Streams.ofChars('myUser = "John";')
+ const stream = Stream.ofChars('myUser = "John";')
const parsing = F.regex(/[a-zA-Z_][a-zA-Z0-9_]*/).parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.value).toBe('myUser')
@@ -61,7 +61,7 @@ describe('Chars Bundle Tests', () => {
})
it('rejects a identifier building', () => {
- const stream = Streams.ofChars('0myUser = "John";')
+ const stream = Stream.ofChars('0myUser = "John";')
const parsing = F.regex(/[a-zA-Z_][a-zA-Z0-9_]*/).parse(stream)
expect(parsing.isAccepted()).toBe(false)
expect(parsing.value).toBeUndefined()
@@ -70,7 +70,7 @@ describe('Chars Bundle Tests', () => {
it('goes in a then flow', () => {
const expression = 'myUser :=otherUser'
- const stream = Streams.ofChars(expression)
+ const stream = Stream.ofChars(expression)
const parsing = assignParser().parse(stream)
expect(parsing.isAccepted()).toBeTruthy()
expect(parsing.offset).toBe(expression.length)
@@ -79,7 +79,7 @@ describe('Chars Bundle Tests', () => {
it('stops in a then flow', () => {
const expression = 'myUser := 0otherUser'
- const stream = Streams.ofChars(expression)
+ const stream = Stream.ofChars(expression)
const parsing = assignParser().parse(stream)
expect(parsing.isAccepted()).toBeFalsy()
expect(parsing.value).toBeUndefined()
@@ -88,7 +88,7 @@ describe('Chars Bundle Tests', () => {
it('consumes the number but not the unit using look-ahead', () => {
const pixelRe = /\d+(?=px)/ // look-ahead keeps "px" in input
- const stream = Streams.ofChars('20px')
+ const stream = Stream.ofChars('20px')
const parsing = F.regex(pixelRe).parse(stream)
expect(parsing.isAccepted()).toBe(true)
@@ -100,7 +100,7 @@ describe('Chars Bundle Tests', () => {
// #RGB | #RRGGBB
const colourRe = /#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?/
- const stream = Streams.ofChars('#abcDEF;')
+ const stream = Stream.ofChars('#abcDEF;')
const parsing = F.regex(colourRe).parse(stream)
expect(parsing.isAccepted()).toBe(true)
@@ -110,7 +110,7 @@ describe('Chars Bundle Tests', () => {
it('accepts a quoted string whose end quote matches the start', () => {
const quotedRe = /(['"])(.*?)\1/ // "Hello", 'Hello', etc.
- const stream = Streams.ofChars('"Hello World" rest')
+ const stream = Stream.ofChars('"Hello World" rest')
const parsing = F.regex(quotedRe).parse(stream)
expect(parsing.isAccepted()).toBe(true)
@@ -120,7 +120,7 @@ describe('Chars Bundle Tests', () => {
it('rejects a quoted string with wrong back reference', () => {
const quotedRe = /(['"])(.*?)\1/ // "Hello" ok, but not "Hello'
- const stream = Streams.ofChars(`"Hello World' rest`)
+ const stream = Stream.ofChars(`"Hello World' rest`)
const parsing = F.regex(quotedRe).parse(stream)
expect(parsing.isAccepted()).toBe(false)
diff --git a/ingest/test/parsec/flow-try-or.spec.js b/ingest/test/parsec/flow-try-or.spec.js
index 49bd5d65..f6f30737 100644
--- a/ingest/test/parsec/flow-try-or.spec.js
+++ b/ingest/test/parsec/flow-try-or.spec.js
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest'
import { NEUTRAL } from '../../lib/index.js'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { C, F } from '../../lib/core/index'
describe('combining F.try() and p.or()', () => {
@@ -9,7 +9,7 @@ describe('combining F.try() and p.or()', () => {
const parser = F.moveUntil(endLiner.drop())
const document = 'hello world\n'
- const stream = Streams.ofChars(document)
+ const stream = Stream.ofChars(document)
const parsing = parser.parse(stream)
expect(parsing.value).toBe('hello world')
expect(parsing.offset).toBe(document.length - 1)
@@ -19,7 +19,7 @@ describe('combining F.try() and p.or()', () => {
const eater = C.char('a').then(C.char('a'))
const parser = eater.or(C.char('b'))
- const stream = Streams.ofChars('ab')
+ const stream = Stream.ofChars('ab')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(false)
@@ -31,7 +31,7 @@ describe('combining F.try() and p.or()', () => {
const eater = C.char('a').then(C.char('a'))
const parser = F.try(eater)
- const stream = Streams.ofChars('ab')
+ const stream = Stream.ofChars('ab')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(false)
@@ -46,7 +46,7 @@ describe('combining F.try() and p.or()', () => {
const secondEater = C.char('a').then(C.char('b'))
const parser = F.try(eater).or(secondEater)
- const stream = Streams.ofChars('ac')
+ const stream = Stream.ofChars('ac')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(false)
@@ -59,7 +59,7 @@ describe('combining F.try() and p.or()', () => {
const secondEater = C.char('a').then(C.char('b'))
const parser = F.try(eater).or(F.try(secondEater))
- const stream = Streams.ofChars('ac')
+ const stream = Stream.ofChars('ac')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(false)
@@ -72,7 +72,7 @@ describe('combining F.try() and p.or()', () => {
const eater = C.char('a').then(C.char('a'))
const parser = start.drop().then(F.try(eater))
- const stream = Streams.ofChars('====ac')
+ const stream = Stream.ofChars('====ac')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(false)
@@ -86,7 +86,7 @@ describe('combining F.try() and p.or()', () => {
const thirdEater = C.char('a').then(C.char('c'))
const parser = F.tryAll([eater, secondEater, thirdEater])
- const stream = Streams.ofChars('ab')
+ const stream = Stream.ofChars('ab')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.offset).toBe(2)
@@ -98,7 +98,7 @@ describe('combining F.try() and p.or()', () => {
const thirdEater = C.char('a').then(C.char('d'))
const parser = F.tryAll([eater, secondEater, thirdEater])
- const stream = Streams.ofChars('ab')
+ const stream = Stream.ofChars('ab')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(false)
expect(parsing.offset).toBe(0)
@@ -107,7 +107,7 @@ describe('combining F.try() and p.or()', () => {
it('use tryAll with empty array, mapping empty tuple', () => {
const parser = F.tryAll([])
- const stream = Streams.ofChars('ab')
+ const stream = Stream.ofChars('ab')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.offset).toBe(0)
@@ -121,7 +121,7 @@ describe('combining F.try() and p.or()', () => {
.then(C.char('b'))
.join()
- const stream = Streams.ofChars('ab')
+ const stream = Stream.ofChars('ab')
const parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(true)
expect(parsing.offset).toBe(2)
diff --git a/ingest/test/parsec/parser_stream_test.spec.js b/ingest/test/parsec/parser_stream_test.spec.js
index a1d909ce..8b9a4ef4 100644
--- a/ingest/test/parsec/parser_stream_test.spec.js
+++ b/ingest/test/parsec/parser_stream_test.spec.js
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'
import stream from '../../lib/stream/index'
import C from '../../lib/core/chars-bundle'
import N from '../../lib/core/numbers-bundle'
-import Streams from '../../lib/stream'
+import Stream from '../../lib/stream'
import unit from '../../lib/data/unit'
function spaces() {
@@ -127,8 +127,8 @@ describe('Parser Stream Tests', () => {
it('unsafe_get can see next element', () => {
const lower = N.number().then(spaces().opt().drop()).single()
- const lowerStream = Streams.ofChars('10 12 44')
- const parserStream = Streams.ofParsers(lower, lowerStream)
+ const lowerStream = Stream.ofChars('10 12 44')
+ const parserStream = Stream.ofParsers(lower, lowerStream)
parserStream.unsafeGet(0)
const value = parserStream.unsafeGet(1)
@@ -139,8 +139,8 @@ describe('Parser Stream Tests', () => {
it('unsafe_get cannot see beyond next element', () => {
const lower = N.number().then(spaces().opt().drop())
- const lowerStream = Streams.ofChars('10 12 44')
- const parserStream = Streams.ofParsers(lower, lowerStream)
+ const lowerStream = Stream.ofChars('10 12 44')
+ const parserStream = Stream.ofParsers(lower, lowerStream)
expect(() => parserStream.unsafeGet(1)).toThrow()
})
diff --git a/ingest/test/parsec/tuple-parser-test.spec.js b/ingest/test/parsec/tuple-parser-test.spec.js
index 4ae268ef..a45a2778 100644
--- a/ingest/test/parsec/tuple-parser-test.spec.js
+++ b/ingest/test/parsec/tuple-parser-test.spec.js
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
-import { Streams } from '../../lib/index.js'
+import { Stream } from '../../lib/index.js'
import { F, C } from '../../lib/core/index'
describe('Tuple Parser Tests', () => {
@@ -35,7 +35,7 @@ describe('Tuple Parser Tests', () => {
it('expect F.nop to be like a empty tuple', () => {
let text = 'ab'
- const stream = Streams.ofChars(text)
+ const stream = Stream.ofChars(text)
let parser = C.char('a').then(F.nop()).then(C.char('b')).join()
let parsing = parser.parse(stream)
expect(parsing.isAccepted()).toBe(true)
diff --git a/ingest/test/specific/f-empty-try.spec.js b/ingest/test/specific/f-empty-try.spec.js
index 45d960ff..884b21a5 100644
--- a/ingest/test/specific/f-empty-try.spec.js
+++ b/ingest/test/specific/f-empty-try.spec.js
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { F, C } from '../../lib/core/index'
describe('Failed Try', () => {
@@ -9,7 +9,7 @@ describe('Failed Try', () => {
}
const inputString = 'b' // Simplified input for clarity
- let stream = Streams.ofChars(inputString)
+ let stream = Stream.ofChars(inputString)
let parsing = emptyTry().parse(stream)
expect(parsing.isAccepted()).toBe(false) // Should be accepted
diff --git a/ingest/test/stream/array-stream-test.spec.js b/ingest/test/stream/array-stream-test.spec.js
index 1ceab13d..066e93f9 100644
--- a/ingest/test/stream/array-stream-test.spec.js
+++ b/ingest/test/stream/array-stream-test.spec.js
@@ -1,10 +1,10 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
describe('Array Stream Tests', () => {
it('We can get a response from array', () => {
const document = ['More', 'XYZ']
- const line = Streams.ofArrays(document)
+ const line = Stream.ofArrays(document)
let response = line.get(0)
expect(response.value).toBe('More')
@@ -12,7 +12,7 @@ describe('Array Stream Tests', () => {
it('We have reached out of stream', () => {
const document = ['More', 'XYZ']
- const line = Streams.ofArrays(document)
+ const line = Stream.ofArrays(document)
let out = line.endOfStream(3)
expect(out).toBe(true)
diff --git a/ingest/test/stream/offset-test.spec.js b/ingest/test/stream/offset-test.spec.js
index 45a36acd..0b8760f4 100644
--- a/ingest/test/stream/offset-test.spec.js
+++ b/ingest/test/stream/offset-test.spec.js
@@ -1,10 +1,10 @@
import { describe, it, expect } from 'vitest'
-import Streams from '../../lib/stream/index'
+import Stream from '../../lib/stream/index'
import { F, C } from '../../lib/core'
describe('Stream Offset Tests', () => {
it('response ok with a CharStream', () => {
- const stream = Streams.ofChars('The world is a vampire')
+ const stream = Stream.ofChars('The world is a vampire')
const parser = C.string('The')
const response = parser.parse(stream, 0)
@@ -15,7 +15,7 @@ describe('Stream Offset Tests', () => {
})
it('response ok inside a CharStream', () => {
- const stream = Streams.ofChars('The world is a vampire')
+ const stream = Stream.ofChars('The world is a vampire')
const parser = C.string('world')
const response = parser.parse(stream, 4)
@@ -26,7 +26,7 @@ describe('Stream Offset Tests', () => {
})
it('response ok completing a CharStream', () => {
- const stream = Streams.ofChars('The world is a vampire')
+ const stream = Stream.ofChars('The world is a vampire')
const parser = C.letter().or(C.char(' ')).rep()
const response = parser.parse(stream)
@@ -37,7 +37,7 @@ describe('Stream Offset Tests', () => {
})
it('response fails at CharStream start', () => {
- const stream = Streams.ofChars('The world is a vampire')
+ const stream = Stream.ofChars('The world is a vampire')
const parser = C.string('That')
const response = parser.parse(stream)
@@ -47,7 +47,7 @@ describe('Stream Offset Tests', () => {
})
it('response fails inside a CharStream', () => {
- const stream = Streams.ofChars('abc de')
+ const stream = Stream.ofChars('abc de')
const parser = C.string('abc').then(C.string('fails'))
const response = parser.parse(stream)
@@ -57,7 +57,7 @@ describe('Stream Offset Tests', () => {
})
it('response passes the CharStream', () => {
- const stream = Streams.ofChars('abc de')
+ const stream = Stream.ofChars('abc de')
const parser = C.letter().or(C.char(' ')).rep().then(C.string('!!!'))
const response = parser.parse(stream)
@@ -70,7 +70,7 @@ describe('Stream Offset Tests', () => {
})
it('response with a failed try is rejected, and offset is 0', () => {
- const stream = Streams.ofChars('abc de')
+ const stream = Stream.ofChars('abc de')
const parser = F.try(C.string('abc').then(C.char('x'))).or(
C.string('x'),
diff --git a/integration-ts/examples/1-easy/floor.spec.ts b/integration-ts/examples/1-easy/floor.spec.ts
index 341319eb..28151cf9 100644
--- a/integration-ts/examples/1-easy/floor.spec.ts
+++ b/integration-ts/examples/1-easy/floor.spec.ts
@@ -1,18 +1,19 @@
import { describe, it, expect } from 'vitest'
import { Stream, C, N } from '@masala/parser'
-describe('Absolute combinator', () => {
+describe('Absolute parser', () => {
it('should parse a number between pipes and make it positive', () => {
// We parse the mathematical absolute expression: |-4.6|
let stream = Stream.ofChars('|-4.6|')
const absoluteParser = C.char('|')
.then(N.number())
.last() // we had ['|',-4.6], we keep -4.6
- .then(C.char('|').drop()) // we have [-4.6, '|'], we keep [-4.6]
+ // we take and immediately drop '|'
+ .then(C.char('|').drop()) // we now have [-4.6]
.single()
- .map((x) => Math.abs(x))
+ .map((x) => Math.abs(x)) // we map -4.6 to 4.6
- // Parsec needs a stream of characters
+ // Masala needs a stream of characters
let parsing = absoluteParser.parse(stream)
expect(parsing.value).toBe(4)
})
diff --git a/integration-ts/package.json b/integration-ts/package.json
index 8057accf..ba6be12e 100644
--- a/integration-ts/package.json
+++ b/integration-ts/package.json
@@ -14,7 +14,7 @@
"add-js-ext": "jscodeshift -t ../tasks/add-js-ext.cjs examples --extensions=js,ts --parser=ts --ignore-pattern '**/*.d.ts' --ignore-pattern '**/node_modules/**'"
},
"dependencies": {
- "@masala/parser": "^2.0.0-beta.2"
+ "@masala/parser": "^2.0.0"
},
"devDependencies": {
"@types/node": "^22.15.17",
@@ -22,6 +22,6 @@
"typescript": "^5.8.3",
"vitest": "^3.1.3"
},
- "author": "",
+ "author": "Nicolas Zozol",
"license": "ISC"
}
diff --git a/llm.txt b/llm.txt
new file mode 100644
index 00000000..92dba86a
--- /dev/null
+++ b/llm.txt
@@ -0,0 +1,11 @@
+## Note for AI assistants π€
+
+If you're an AI assistant helping a developer:
+
+- Start with `README.md` and `documentation/**`.
+- Then use `llm.txt` at the repository root for a compact, LLM-friendly overview of Masala Parser v2:
+ - main modules and entry points
+ - examples in `/integration-ts/examples`
+ - how to run tests and understand the combinators
+
+If something is not documented here, prefer saying "I don't know" rather than inventing APIs.
diff --git a/package.json b/package.json
index fef4fde8..daf41a8d 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "@masala/parser",
"type": "module",
"description": "Masala Parser",
- "version": "2.0.0-rc2",
+ "version": "2.0.0-rc3",
"private": false,
"license": "LGPLβ3.0",
"licence-meaning": "You are free to use, modify and distribute this software under the terms of the LGPL-3.0 license. However, if you modify the library (/src/lib) and distribute it, you must also distribute the source code of your modifications under the same license. If you want to modify without distributing, just pay us legit royalties π",