Skip to content

Commit

Permalink
fix(parsers/integer): fix regexps, options & tests
Browse files Browse the repository at this point in the history
  • Loading branch information
norskeld committed Nov 28, 2021
1 parent 78fa9af commit 2421d6d
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 119 deletions.
20 changes: 8 additions & 12 deletions src/internal/parsers/integer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,29 @@ import { success, Parser, State } from '../state'

import { regexp } from './regexp'

type SignKind = 'any' | 'some' | 'positive' | 'negative' | 'none'
type SignKind = 'always' | 'never' | 'maybe'
type SignMeta = [rexpression: RegExp, expectation: string]

interface IntegerOptions {
sign?: SignKind
radix?: number
}

const INT_SOME_RE = /^[+-]?\d+$/g
const INT_ANY_RE = /^[+-]\d+$/g
const INT_NONE_RE = /^\d+$/g
const INT_POSITIVE_RE = /^[+]\d+$/g
const INT_NEGATIVE_RE = /^[-]\d+$/g
const INT_ALWAYS_RE = /-\d+/g
const INT_NEVER_RE = /\d+/g
const INT_MAYBE_RE = /-?\d+/g

function meta(sign: SignKind): SignMeta {
// prettier-ignore
switch (sign) {
case 'some': return [INT_SOME_RE, `optionally signed integer`]
case 'any': return [INT_ANY_RE, `signed integer`]
case 'none': return [INT_NONE_RE, `unsigned integer`]
case 'positive': return [INT_POSITIVE_RE, `positively signed integer`]
case 'negative': return [INT_NEGATIVE_RE, `negatively signed integer`]
case 'always': return [INT_ALWAYS_RE, 'signed integer']
case 'never': return [INT_NEVER_RE, 'unsigned integer']
case 'maybe': return [INT_MAYBE_RE, 'optionally signed integer']
}
}

export function integer(options: IntegerOptions = {}): Parser<number> {
const sign = options.sign ?? 'none'
const sign = options.sign ?? 'maybe'
const radix = options.radix ?? 10

return {
Expand Down
168 changes: 61 additions & 107 deletions tests/internal/parsers/integer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,140 +3,94 @@ import { integer } from '@lib/internal/parsers/integer'

import { result, run, should } from '@tests/@helpers'

describe(`integer (sign = ...) (success)`, () => {
it(`should succeed if given an unsigned integer number`, () => {
const actual = run(integer(), '42')
const expected = result('success', 42)
describe(integer, () => {
describe('sign = ... (success)', () => {
it('should succeed if given an unsigned integer number', () => {
const actual = run(integer(), '42')
const expected = result('success', 42)

const actualSingle = run(integer(), '0')
const expectedSingle = result('success', 0)
const actualSingle = run(integer(), '0')
const expectedSingle = result('success', 0)

should.matchState(actual, expected)
should.matchState(actualSingle, expectedSingle)
})

it(`should succeed if configured to accept optionally unsigned integers (sign = some)`, () => {
const cases = [
[integer({ sign: 'some' }), '+42', result('success', 42)],
[integer({ sign: 'some' }), '-42', result('success', -42)],
[integer({ sign: 'some' }), '42', result('success', 42)]
] as const

cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
should.matchState(actual, expected)
should.matchState(actualSingle, expectedSingle)
})
})

it(`should succeed if configured to accept signed integers (sign = any)`, () => {
const cases = [
[integer({ sign: 'any' }), '+42', result('success', 42)],
[integer({ sign: 'any' }), '-42', result('success', -42)]
] as const
it('should succeed if configured to accept optionally unsigned integers (sign = maybe)', () => {
const cases = [
[integer({ sign: 'maybe' }), '-42', result('success', -42)],
[integer({ sign: 'maybe' }), '42', result('success', 42)]
] as const

cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
})
})
})

it(`should succeed if configured to not accept signed integers (sign = none)`, () => {
const cases = [
[integer({ sign: 'none' }), '42', result('success', 42)],
[integer({ sign: 'none' }), '8912493', result('success', 8912493)]
] as const
it('should succeed if configured to accept signed integers (sign = always)', () => {
const cases = [
[integer({ sign: 'always' }), '-8', result('success', -8)],
[integer({ sign: 'always' }), '-42', result('success', -42)],
[integer({ sign: 'always' }), '-9000', result('success', -9000)]
] as const

cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
})
})
})

it(`should succeed if configured to accept positively signed integers (sign = positive)`, () => {
const cases = [
[integer({ sign: 'positive' }), '+42', result('success', 42)],
[integer({ sign: 'positive' }), '+8912493', result('success', 8912493)]
] as const
it('should succeed if configured to not accept signed integers (sign = never)', () => {
const cases = [
[integer({ sign: 'never' }), '42', result('success', 42)],
[integer({ sign: 'never' }), '8912493', result('success', 8912493)]
] as const

cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
})
})
})

it(`should succeed if configured to accept negatively signed integers (sign = positive)`, () => {
const cases = [
[integer({ sign: 'negative' }), '-42', result('success', -42)],
[integer({ sign: 'negative' }), '-8912493', result('success', -8912493)]
] as const
describe('radix = ... (success)', () => {
it('should succeed with hexadecimal value if configured to use radix = 16', () => {
const actual = run(integer({ radix: 16 }), '100')
const expected = result('success', 256)

cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
should.matchState(actual, expected)
})
})
})

describe(`integer (radix = ...) (success)`, () => {
it(`should succeed with hexadecimal value if configured to use radix = 16`, () => {
const actual = run(integer({ radix: 16 }), '100')
const expected = result('success', 256)
it('should succeed with hexadecimal value if configured to use radix = 8', () => {
const actual = run(integer({ radix: 8 }), '100')
const expected = result('success', 64)

should.matchState(actual, expected)
})
should.matchState(actual, expected)
})

it(`should succeed with hexadecimal value if configured to use radix = 8`, () => {
const actual = run(integer({ radix: 8 }), '100')
const expected = result('success', 64)
it('should succeed with hexadecimal value if configured to use radix = 2', () => {
const actual = run(integer({ radix: 2 }), '100')
const expected = result('success', 4)

should.matchState(actual, expected)
should.matchState(actual, expected)
})
})

it(`should succeed with hexadecimal value if configured to use radix = 2`, () => {
const actual = run(integer({ radix: 2 }), '100')
const expected = result('success', 4)
describe('failed cases', () => {
it('should fail if configured to accept only signed integers and given an unsigned integer', () => {
const message = 'signed integer'

should.matchState(actual, expected)
})
})
const actual = run(error(integer({ sign: 'always' }), message), '420')
const expected = result('failure', message)

describe(`integer (failure)`, () => {
function withTestCases(value: string) {
const message = 'integer'

const cases = [
[error(integer(), message), `${value}`, result('failure', message)],
[error(integer({ sign: 'some' }), message), `+${value}`, result('failure', message)],
[error(integer({ sign: 'some' }), message), `-${value}`, result('failure', message)],
[error(integer({ sign: 'some' }), message), `${value}`, result('failure', message)],
[error(integer({ sign: 'any' }), message), `-${value}`, result('failure', message)],
[error(integer({ sign: 'any' }), message), `+${value}`, result('failure', message)],
[error(integer({ sign: 'none' }), message), `${value}`, result('failure', message)],
[error(integer({ sign: 'positive' }), message), `+${value}`, result('failure', message)],
[error(integer({ sign: 'negative' }), message), `-${value}`, result('failure', message)]
] as const

cases.forEach(([parser, input, expected]) => {
should.matchState(run(parser, input), expected)
should.matchState(actual, expected)
})
}

it(`should fail if given a float`, () => {
withTestCases('42.00')
withTestCases('0.42')
})

it(`should fail if given an exponential`, () => {
withTestCases('0e-5')
withTestCases('2e+3')
withTestCases('1e3')
})
it('should fail if configured to accept only unsigned integers and given a signed integer', () => {
const message = 'unsigned integer'

it(`should fail if given a binary`, () => {
withTestCases('0b100')
})
const actual = run(error(integer({ sign: 'never' }), message), '-420')
const expected = result('failure', message)

it(`should fail if given an octal`, () => {
withTestCases('0o644')
})

it(`should fail if given a hexadecimal`, () => {
withTestCases('0xd3ad')
withTestCases('0xf00d')
should.matchState(actual, expected)
})
})
})

0 comments on commit 2421d6d

Please sign in to comment.