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 25, 2023
1 parent 4031c9e commit f37fe32
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const re = parse('!re /fo./g', { customTags: [regexp] })
- `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 that 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 an example.

The function and class values created by parsing `!function` and
`!class` tags will not actually replicate running code, but
Expand All @@ -43,6 +48,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
84 changes: 84 additions & 0 deletions src/bigint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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('parse hex, octal, binary', t => {
const cases = [
'0b11011110101011011011111011101111',
'0b11011110101011011011111011101111n',
'0o33653337357',
'0o33653337357n',
'3735928559',
'3735928559n',
'0xDeAdBeEf',
'0xDeAdBeEfn',
'0xDEADBEEF',
'0xDEADBEEFn',
'0xdeadbeef',
'0xdeadbeefn'
]
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 0Xbad1dea\n', opt), '0x must be lowercase')
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))], {
customTags
}),
`- 123
- !bigint 123n
- !bigint 123n
- !bigint 123n
- !bigint 123n
`
)

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

const identify = (value: any) => {
return typeof value === 'bigint' || value instanceof BigInt
}

/**
* `!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 = {
identify,
tag: '!bigint',
resolve(str: string) {
const match = str.match(/^([1-9][0-9]*|0x[0-9a-fA-F]+|0o[0-7]+|0b[01]+)n?$/)
if (!match) throw new Error('Invalid BigInt value')
return BigInt(match[1])
},
stringify(item: Scalar, ctx: StringifyContext, onComment, onChompKeep) {
if (!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)
}
} satisfies ScalarTag
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 f37fe32

Please sign in to comment.