generated from norskeld/serpent
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(parsers/integer): add
integer
parser
See tests for examples.
- Loading branch information
Showing
3 changed files
with
198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { success, Parser, State } from '../state' | ||
|
||
import { regexp } from '../combinators/regexp' | ||
|
||
type SignKind = 'any' | 'some' | 'positive' | 'negative' | 'none' | ||
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 | ||
|
||
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`] | ||
} | ||
} | ||
|
||
export function integer(options: IntegerOptions = {}): Parser<number> { | ||
const sign = options.sign ?? 'none' | ||
const radix = options.radix ?? 10 | ||
|
||
return { | ||
parse(state: State) { | ||
const [rexpression, expectation] = meta(sign) | ||
const result = regexp(rexpression, expectation).parse(state) | ||
|
||
switch (result.kind) { | ||
case 'success': { | ||
return success(result.state, parseInt(result.value, radix)) | ||
} | ||
|
||
case 'failure': { | ||
return result | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
export { integer as int } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './internal/parsers/integer' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import * as exposed from '@lib/parsers' | ||
import { error } from '@lib/combinators' | ||
import { integer } from '@lib/parsers' | ||
|
||
import { result, run, should } from '@tests/@setup/jest.helpers' | ||
|
||
describe('internal/parsers/integer', () => { | ||
it(`should expose 'integer' ('int')`, () => { | ||
should.expose(exposed, 'integer', 'int') | ||
}) | ||
|
||
describe(`integer (sign = ...) (success)`, () => { | ||
it(`should succeed if given an unsigned integer number`, () => { | ||
const actual = run(integer(), '42') | ||
const expected = result('success', 42) | ||
|
||
should.matchState(actual, expected) | ||
}) | ||
|
||
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) | ||
}) | ||
}) | ||
|
||
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 | ||
|
||
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 | ||
|
||
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 | ||
|
||
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 | ||
|
||
cases.forEach(([parser, input, expected]) => { | ||
should.matchState(run(parser, input), 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) | ||
|
||
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) | ||
|
||
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) | ||
|
||
should.matchState(actual, expected) | ||
}) | ||
}) | ||
|
||
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) | ||
}) | ||
} | ||
|
||
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 given a binary`, () => { | ||
withTestCases('0b100') | ||
}) | ||
|
||
it(`should fail if given an octal`, () => { | ||
withTestCases('0o644') | ||
}) | ||
|
||
it(`should fail if given a hexadecimal`, () => { | ||
withTestCases('0xd3ad') | ||
withTestCases('0xf00d') | ||
}) | ||
}) | ||
}) |