Skip to content

Commit

Permalink
feat: CodeReader
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <[email protected]>
  • Loading branch information
unicornware committed Jun 11, 2024
1 parent 2a803f4 commit 7fca10d
Show file tree
Hide file tree
Showing 20 changed files with 453 additions and 34 deletions.
1 change: 1 addition & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,4 @@ ignore:
profiling:
critical_files_paths:
- src/character.reader.ts
- src/code.reader.ts
4 changes: 3 additions & 1 deletion .github/infrastructure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ repository:
automated_security_fixes: true
default_branch: main
delete_branch_on_merge: true
description: vfile utility to read characters from a file
description: vfile utility to read from a file
has_issues: true
has_projects: true
has_wiki: false
Expand All @@ -184,7 +184,9 @@ repository:
squash_merge_commit_title: PR_TITLE
topics:
- character
- code-point
- reader
- unicode
- vfile
- vfile-util
visibility: public
Expand Down
76 changes: 57 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[![vitest](https://img.shields.io/badge/-vitest-6e9f18?style=flat&logo=vitest&logoColor=ffffff)](https://vitest.dev/)
[![yarn](https://img.shields.io/badge/-yarn-2c8ebb?style=flat&logo=yarn&logoColor=ffffff)](https://yarnpkg.com/)

[vfile][vfile] utility to read characters from a file
[vfile][vfile] utility to read from a file

## Contents

Expand All @@ -36,8 +36,11 @@
- [`Reader#start`](#readerstart)
- [`CharacterReader(file[, start])`](#characterreaderfile-start)
- [`CharacterReader#peekMatch(test)`](#characterreaderpeekmatchtest)
- [`CodeReader(file[, start])`](#codereaderfile-start)
- [`CodeReader#stringify(...codes)`](#codereaderstringifycodes)
- [`CharacterMatch`](#charactermatch)
- [`Character`](#character)
- [`Code`](#code)
- [`ReaderIterator<T>`](#readeriteratort)
- [`ReaderIteratorResult`](#readeriteratorresult)
- [`ReaderValue`](#readervalue)
Expand All @@ -46,12 +49,12 @@

## What is this?

This package implements an input reader that can be used to read characters from a file.
This package implements an input reader that can be used to read characters and code points from a file.

## When should I use this?

This package is useful when characters need to be read individually or by regex match, such as when building a parser or
tokenizer.
This package is useful when characters or code points need to be processed individually or as a group, such as when
building a parser or tokenizer.

## Install

Expand All @@ -73,42 +76,50 @@ yarn add @flex-development/vfile-reader
In Deno with [`esm.sh`][esmsh]:

```ts
import { CharacterReader } from 'https://esm.sh/@flex-development/vfile-reader'
import { CharacterReader, CodeReader } from 'https://esm.sh/@flex-development/vfile-reader'
```

In browsers with [`esm.sh`][esmsh]:

```html
<script type="module">
import { CharacterReader } from 'https://esm.sh/@flex-development/vfile-reader'
import { CharacterReader, CodeReader } from 'https://esm.sh/@flex-development/vfile-reader'
</script>
```

## Use

```ts
import { CharacterReader } from '@flex-development/vfile-reader'
import { CharacterReader, CodeReader } from '@flex-development/vfile-reader'
import { read } from 'to-vfile'
import type { VFile } from 'vfile'

const file: VFile = await read('__fixtures__/emojis.txt') // 😍👍🚀❇️

const chars: CharacterReader = new CharacterReader(file)
const codes: CodeReader = new CodeReader(file)

// for (const char of chars) console.dir({ char, now: chars.now() })

while (!chars.eof) console.dir({ char: chars.read(), now: chars.now() })
// for (const code of codes) console.dir({ code, now: codes.now() })

while (!chars.eof) {
console.dir({
char: chars.read(),
code: codes.read(),
now: codes.now()
})
}
```

...yields

```sh
{ char: '😍', now: { column: 1, line: 1, offset: 0 } }
{ char: '👍', now: { column: 2, line: 1, offset: 1 } }
{ char: '🚀', now: { column: 3, line: 1, offset: 2 } }
{ char: '', now: { column: 4, line: 1, offset: 3 } }
{ char: '', now: { column: 5, line: 1, offset: 4 } }
{ char: '\n', now: { column: 6, line: 1, offset: 5 } }
{ char: '😍', code: 128525, now: { column: 1, line: 1, offset: 0 } }
{ char: '👍', code: 128077, now: { column: 2, line: 1, offset: 1 } }
{ char: '🚀', code: 128640, now: { column: 3, line: 1, offset: 2 } }
{ char: '', code: 10055, now: { column: 4, line: 1, offset: 3 } }
{ char: '', code: 65039, now: { column: 5, line: 1, offset: 4 } }
{ char: '\n', code: 10, now: { column: 6, line: 1, offset: 5 } }
```
## API
Expand Down Expand Up @@ -251,7 +262,7 @@ position of the reader.
### `CharacterReader(file[, start])`
> **extends**: [`Reader`](#readerfile-start)
> **extends**: `Reader<Character>`
Create a new character reader.
Expand All @@ -267,6 +278,24 @@ Get the next match from the file without changing the position of the reader, wi
([`CharacterMatch`](#charactermatch)) Peeked character match or `null`.
### `CodeReader(file[, start])`
> **extends**: `Reader<Code>`
Create a new code point reader.
#### `CodeReader#stringify(...codes)`
Convert the specified sequence of code points to a string.
##### `Parameters`
- `...codes` ([`Code[]`](#code)) &mdash; code points sequence
##### `Returns`
(`string`) String created from code point sequence.
### `CharacterMatch`
Match in a source file, with `null` denoting no match (TypeScript type).
Expand All @@ -277,12 +306,21 @@ type CharacterMatch = RegExpExecArray | null
### `Character`
Character in a source file, with `null` denoting the end of file (TypeScript type).
Character in a source file, with `null` denoting end of file (TypeScript type).
```ts
type Character = string | null
```
### `Code`
An integer between `0` and `0x10FFFF` (inclusive) representing a Unicode code point in a source file, with `null`
denoting end of file (TypeScript type).
```ts
type Code = number | null
```
### `ReaderIterator<T>`
Input reader iterator API (TypeScript interface).
Expand All @@ -306,10 +344,10 @@ type ReaderIteratorResult<
### `ReaderValue`
Union of input reader output values (TypeScript type).
Character or code point in a source file, with `null` denoting the end of file (TypeScript type).
```ts
type ReaderValue = Character
type ReaderValue = Character | Code
```
## Types
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"name": "@flex-development/vfile-reader",
"description": "vfile utility to read characters from a file",
"description": "vfile utility to read from a file",
"version": "1.0.2",
"keywords": [
"character",
"code-point",
"reader",
"unicode",
"vfile",
"vfile-util"
],
Expand Down
12 changes: 12 additions & 0 deletions src/__snapshots__/code.reader.functional.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`functional:CodeReader > iterator > should iterate over all code points 1`] = `
[
128525,
128077,
128640,
10055,
65039,
10,
]
`;
114 changes: 114 additions & 0 deletions src/__tests__/code.reader.functional.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* @file Functional Tests - CodeReader
* @module vfile-reader/tests/functional/CodeReader
*/

import type { Code } from '#src/types'
import type { MockInstance } from '#tests/interfaces'
import { read } from 'to-vfile'
import type { VFile } from 'vfile'
import Reader from '../abstract.reader'
import TestSubject from '../code.reader'

describe('functional:CodeReader', () => {
let file: VFile
let subject: TestSubject

beforeAll(async () => {
file = await read('__fixtures__/emojis.txt')
subject = new TestSubject(file)
})

describe('#peek', () => {
let spy: MockInstance<Reader['peek']>

beforeEach(() => {
spy = vi.spyOn(Reader.prototype, 'peek')
})

it('should call Reader.prototype.peek', () => {
// Arrange
const k: number = 3

// Act
subject.peek(k)

// Expect
expect(spy).toHaveBeenCalledOnce()
expect(spy).toHaveBeenCalledWith(k)
})
})

describe('#read', () => {
let spy: MockInstance<Reader['read']>

beforeEach(() => {
spy = vi.spyOn(Reader.prototype, 'read')
})

it('should call Reader.prototype.read', () => {
// Arrange
const k: number = 0

// Act
subject.read(k)

// Expect
expect(spy).toHaveBeenCalledOnce()
expect(spy).toHaveBeenCalledWith(k)
})
})

describe('#slice', () => {
let spy: MockInstance<Reader['slice']>

beforeEach(() => {
spy = vi.spyOn(Reader.prototype, 'slice')
})

it('should call Reader.prototype.slice', () => {
// Arrange
const m: number = 1

// Act
subject.slice(m)

// Expect
expect(spy).toHaveBeenCalledOnce()
expect(spy).toHaveBeenCalledWith(m)
})
})

describe('#stringify', () => {
let spy: MockInstance<(typeof String)['fromCodePoint']>

beforeEach(() => {
spy = vi.spyOn(String, 'fromCodePoint')
})

it('should call String.fromCodePoint', () => {
// Arrange
const codes: Code[] = [subject.peek(0), subject.peek(1), subject.peek(13)]

// Act
subject.stringify(...codes)

// Expect
expect(spy).toHaveBeenCalledOnce()
expect(spy).toHaveBeenCalledWith(...codes)
})
})

describe('iterator', () => {
it('should iterate over all code points', () => {
// Arrange
const codes: Code[] = []

// Act
for (const code of subject) codes.push(code)

// Expect
expect(codes).toMatchSnapshot()
})
})
})
14 changes: 14 additions & 0 deletions src/__tests__/code.reader.spec-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @file Type Tests - CodeReader
* @module vfile-reader/tests/unit-d/CodeReader
*/

import type { Code } from '#src/types'
import type Reader from '../abstract.reader'
import type TestSubject from '../code.reader'

describe('unit-d:CodeReader', () => {
it('should extend Reader<Code>', () => {
expectTypeOf<TestSubject>().toMatchTypeOf<Reader<Code>>()
})
})
32 changes: 32 additions & 0 deletions src/__tests__/code.reader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @file Unit Tests - CodeReader
* @module vfile-reader/tests/unit/CodeReader
*/

import type { Offset } from '@flex-development/unist-util-types'
import { read } from 'to-vfile'
import TestSubject from '../code.reader'

describe('unit:CodeReader', () => {
let index: Offset
let subject: TestSubject

beforeAll(async () => {
subject = new TestSubject(await read('__fixtures__/emojis.txt'))
index = subject.index
})

describe('#output', () => {
it('should return current code point without changing position', () => {
expect(subject.output).to.equal(subject.peek(0))
expect(subject.index).to.eq(index)
})
})

describe('#previous', () => {
it('should return previous code point without changing position', () => {
expect(subject.previous).to.eq(subject.peek(-1))
expect(subject.index).to.eq(index)
})
})
})
6 changes: 5 additions & 1 deletion src/__tests__/index.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import * as testSubject from '../index'

describe('e2e:vfile-reader', () => {
it('should expose public api', () => {
expect(testSubject).to.have.keys(['CharacterReader', 'Reader'])
expect(testSubject).to.have.keys([
'CharacterReader',
'CodeReader',
'Reader'
])
})
})
Loading

0 comments on commit 7fca10d

Please sign in to comment.