diff --git a/doc/parsec/range.md b/doc/parsec/range.md new file mode 100644 index 0000000..07eebab --- /dev/null +++ b/doc/parsec/range.md @@ -0,0 +1,15 @@ +# Parser Combinator: str + +`range('min','max')` consumes a token, if the `text`'s length is 1 and the codepoint of `text` is between that of `min` (included) and `max` (included). It fails if it doesn't match the conditions. + +For example, for passing TypeScript's export statement: + +```typescript +3.142 +``` + +We could write + +```typescript +seq(range('1', '9'), str('.'), str(`142`)) +``` \ No newline at end of file diff --git a/packages/ts-parsec/src/Parsers/ParserInterface.ts b/packages/ts-parsec/src/Parsers/ParserInterface.ts index c9cee46..44059f9 100644 --- a/packages/ts-parsec/src/Parsers/ParserInterface.ts +++ b/packages/ts-parsec/src/Parsers/ParserInterface.ts @@ -88,3 +88,14 @@ export function unableToConsumeToken(token: Token | undefined): Pa message: `Unable to consume token: ${token === undefined ? '' : token.text}` }; } + +export function rangeInvalid( + min : string, + max: string, + token: Token | undefined): ParseError { + return { + kind: 'Error', + pos: token === undefined ? undefined : token.pos, + message: `the range for \`range\` is not valid: min:${min}, max:${max} ` + }; +} diff --git a/packages/ts-parsec/src/Parsers/TokenParser.ts b/packages/ts-parsec/src/Parsers/TokenParser.ts index 5f50e9c..4c0aadb 100644 --- a/packages/ts-parsec/src/Parsers/TokenParser.ts +++ b/packages/ts-parsec/src/Parsers/TokenParser.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { Token } from '../Lexer'; -import { FailedParser, FailedParserOutput, Parser, ParserOutput, unableToConsumeToken } from './ParserInterface'; +import { FailedParser, FailedParserOutput, Parser, ParserOutput, rangeInvalid, unableToConsumeToken } from './ParserInterface'; export function nil(): Parser { return { @@ -73,6 +73,41 @@ export function str(toMatch: string): Parser> { }; } +export function range(min: string, max: string): Parser> { + return { + parse(token: Token | undefined): ParserOutput> { + const rangeUnsuitable = (max.length !== 1) + || (min.length !== 1) + || (min.charCodeAt(0) > max.charCodeAt(0)); + if (rangeUnsuitable) { + return { + successful: false, + error: rangeInvalid(min, max, token) + }; + } + if (token === undefined + || token.text.length !== 1 + || token.text.charCodeAt(0) < min.charCodeAt(0) + || token.text.charCodeAt(0) > max.charCodeAt(0) + ) { + return { + successful: false, + error: unableToConsumeToken(token) + }; + } + return { + candidates: [{ + firstToken: token, + nextToken: token.next, + result: token + }], + successful: true, + error: undefined + }; + } + }; +} + export function tok(toMatch: T): Parser> { return { parse(token: Token | undefined): ParserOutput> { diff --git a/packages/tspc-test/src/TestParser.ts b/packages/tspc-test/src/TestParser.ts index 84c9d80..67870ce 100644 --- a/packages/tspc-test/src/TestParser.ts +++ b/packages/tspc-test/src/TestParser.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as parsec from 'typescript-parsec'; import { buildLexer, Token } from 'typescript-parsec'; -import { alt, alt_sc, apply, errd, kleft, kmid, kright, opt, opt_sc, rep, rep_n, rep_sc, repr, seq, str, tok } from 'typescript-parsec'; +import { alt, alt_sc, apply, errd, kleft, kmid, kright, opt, opt_sc, range, rep, rep_n, rep_sc, repr, seq, str, tok } from 'typescript-parsec'; function notUndefined(t: T | undefined): T { assert.notStrictEqual(t, undefined); @@ -65,6 +65,25 @@ test(`Parser: tok`, () => { } }); +test(`Parser: range`, () => { + const firstToken = notUndefined(lexer.parse(`3,142`)); + { + const result = succeeded(range('1', '9').parse(firstToken)); + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].result.text, '3'); + assert.strictEqual(result[0].firstToken, firstToken); + assert.strictEqual(result[0].nextToken, firstToken.next); + } + { + const result = range('9', '1').parse(firstToken); + assert.strictEqual(result.successful, false); + } + { + const result = range('a', 'z').parse(firstToken); + assert.strictEqual(result.successful, false); + } +}); + test(`Parser: alt`, () => { { const firstToken = notUndefined(lexer.parse(`123,456`));