Skip to content

Commit

Permalink
feat: add !bigint tag
Browse files Browse the repository at this point in the history
This is a little bit weird to use due to having to prepend to the `tags`
list, but the definition does work if used properly.
  • Loading branch information
isaacs committed May 30, 2023
1 parent 4031c9e commit 95817b3
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 3 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,22 @@ const re = parse('!re /fo./g', { customTags: [regexp] })

## Available Types

- `regexp` (`!re`) - [RegExp] values,
using their default `/foo/flags` string representation.
- `sharedSymbol` (`!symbol/shared`) - [Shared Symbols], i.e. ones created with `Symbol.for()`
- `regexp` (`!re`) - [RegExp] values, using their default
`/foo/flags` string representation.
- `sharedSymbol` (`!symbol/shared`) - [Shared Symbols], i.e. ones
created with `Symbol.for()`
- `symbol` (`!symbol`) - [Unique Symbols]
- `nullobject` (`!nullobject) - Object with a `null` prototype
- `error` (`!error`) - JavaScript [Error] objects
- `classTag` (`!class`) - JavaScript [Class] values
- `functionTag` (`!function`) - JavaScript [Function] values
(will also be used to stringify Class values, unless the
`classTag` tag is loaded ahead of `functionTag`)
- `bigint` (`!bigint`) - JavaScript [BigInt] values. Note: in
order to use this effectively, a function must be provided as
`customTags` in order to prepend the `bigint` tag, or else the
built-in `!!int` tags will take priority. See
[bigint.test.ts](./src/bigint.test.ts) for examples.

The function and class values created by parsing `!function` and
`!class` tags will not actually replicate running code, but
Expand All @@ -43,6 +49,7 @@ rather no-op function/class values with matching name and
[Error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
[Function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
[Class]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
[BigInt]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt

## Customising Tag Names

Expand Down
103 changes: 103 additions & 0 deletions src/bigint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { test } from 'tap'
import { Document, parse, stringify, Tags, type Scalar } from 'yaml'

import { bigint } from '.'

const customTags = (tags: Tags) => ([bigint] as Tags).concat(tags)

test('parse with n', t => {
const res: bigint = parse(`!bigint 123n`, {
customTags: [bigint]
})
t.type(res, 'bigint')
t.equal(Number(res), 123)
t.equal(res, 123n)
t.end()
})

test('parse without n', t => {
const res: bigint = parse(`!bigint 123`, { customTags })
t.type(res, 'bigint')
t.equal(Number(res), 123)
t.equal(res, 123n)
t.end()
})

test('negative zero is zero', t => {
const pos: bigint = parse(`!bigint 0`, { customTags })
const neg: bigint = parse(`!bigint -0`, { customTags })
t.equal(pos, 0n, 'pos equals zero')
t.equal(neg, 0n, 'neg equals zero')
t.equal(pos, neg, 'pos equals neg')
t.end()
})

test('parse hex, octal, binary', t => {
const cases = [
'0b11011110101011011011111011101111',
'0B11011110101011011011111011101111',
'0b11011110101011011011111011101111n',
'0B11011110101011011011111011101111n',
'0o33653337357',
'0o33653337357n',
'3735928559',
'3735928559n',
'0xDeAdBeEf',
'0xDeAdBeEfn',
'0xDEADBEEF',
'0xDEADBEEFn',
'0XDEADBEEF',
'0XDEADBEEFn',
'0x0000DEADBEEF',
'0x0000DEADBEEFn',
'0xdeadbeef',
'0xdeadbeefn',
'0000000003735928559'
]
for (const c of cases) {
const res: bigint = parse(`!bigint ${c}`, { customTags })
t.equal(res, 0xdeadbeefn, `${c} value`)
t.type(res, 'bigint', `${c} typeof`)
}
t.end()
})

test('parse invalid', t => {
const opt = { customTags }
t.throws(() => parse('!bigint not a number\n', opt))
t.throws(() => parse('!bigint 123.456\n', opt))
t.throws(() => parse('!bigint 123x\n', opt))
t.throws(() => parse('!bigint 0xBAD1DEAN\n', opt), 'n must be lowercase')
t.throws(() => parse('!bigint 0b012', opt), '2 is invalid binary digit')
t.throws(() => parse('!bigint 0o018', opt), '8 is invalid octal digit')
t.end()
})

test('stringify', t => {
const doc = new Document<Scalar, false>(123n, { customTags })
t.equal(doc.toString(), '!bigint 123n\n')

doc.contents.value = Object(42n)
t.equal(doc.toString(), '!bigint 42n\n')

doc.contents.value = 42
t.throws(() => doc.toString(), { name: 'TypeError' })

t.equal(
stringify(
[123, 123n, BigInt('123'), Object(123n), Object(BigInt(123)), -123n],
{
customTags
}
),
`- 123
- !bigint 123n
- !bigint 123n
- !bigint 123n
- !bigint 123n
- !bigint -123n
`
)

t.end()
})
26 changes: 26 additions & 0 deletions src/bigint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Scalar, ScalarTag } from 'yaml'
import { StringifyContext, stringifyString } from 'yaml/util'

/**
* `!bigint` BigInt
*
* [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) values,
* using their conventional `123n` representation.
*/
export const bigint: ScalarTag = {
identify: (value: any) => {
return typeof value === 'bigint' || value instanceof BigInt
},
tag: '!bigint',
resolve(str: string) {
if (str.endsWith('n')) str = str.substring(0, str.length - 1)
return BigInt(str)
},
stringify(item: Scalar, ctx: StringifyContext, onComment, onChompKeep) {
if (!bigint.identify?.(item.value)) {
throw new TypeError(`${item.value} is not a bigint`)
}
const value = (item.value as BigInt).toString() + 'n'
return stringifyString({ value }, ctx, onComment, onChompKeep)
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { nullobject } from './null-object.js'
export { error } from './error.js'
export { functionTag } from './function.js'
export { classTag } from './class.js'
export { bigint } from './bigint.js'

0 comments on commit 95817b3

Please sign in to comment.