Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
marihachi committed Aug 11, 2022
2 parents 6cfd4ec + a6b93f7 commit b5ddc85
Show file tree
Hide file tree
Showing 13 changed files with 515 additions and 205 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@
-->

## 0.7.0 (2022/08/11)

### Features
- Add api: parser.many(min, terminator)
- Add api: T.sof

### Improvements
- Improves rules validation for T.createLanguage()

### Changes
- Change api: T.regexp(RegExp) -> T.str(RegExp)
- When all input strings could not be consumed, an error is now generated.
- Now returns index value on match failure.

## 0.6.0 (2022/08/06)

### Changes
Expand Down
27 changes: 24 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ console.log(result);
// => { success: true, value: [ 'abc', 'abc' ], index: 6 }
```

## parser.many(min: number, terminator: Parser): Parser
The many() can have a termination condition.

The following example uses many to match strings up to ")".
The terminating condition ")" is not consumed.
```ts
// [Equivalent PEG] "(" (!")" @.)+ ")"
const parser = T.seq([
T.str('('),
T.char.many(1, T.str(')')),
T.str(')'),
]);

const result = parser.parse('(abc)');
console.log(result);
// => { success: true, value: [ '(', [ 'a', 'b', 'c' ], ')' ], index: 5 }
```

## parser.option(): Parser
Generates a new parser that returns null even if the match fails.

Expand Down Expand Up @@ -112,12 +130,12 @@ console.log(result);
// => { success: true, value: 'test', index: 4 }
```

## T.regexp(pattern: Regexp): Parser
## T.str(pattern: Regexp): Parser
Generates a new parser that consumes the input string using the specified regular expression.

```ts
// [Equivalent PEG] [a-z]
const parser = T.regexp(/[a-z]/);
const parser = T.str(/[a-z]/);

const result = parser.parse('a');
console.log(result);
Expand Down Expand Up @@ -272,8 +290,11 @@ Matches `\r\n` (CR + LF)
## T.newline: Parser
Matches `\r\n` or `\r` or `\n`

## T.sof: Parser
Matches start of input string.

## T.eof: Parser
Matches end of input string
Matches end of input string.

```ts
// [Equivalent PEG] "a" !.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "terrario",
"version": "0.6.0",
"version": "0.7.0",
"description": "A simple parser combinator library with TypeScript.",
"main": "./built/index.js",
"types": "./built/index.d.ts",
Expand Down
14 changes: 10 additions & 4 deletions peg-cli.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const { performance } = require('perf_hooks');
const readLine = require('readline');
const { compile } = require('./built/peg/compiler');

//const { compile } = require('./built/peg/compiler');
const { parse } = require('./built/peg/internal/peg-parser');
const { emit } = require('./built/peg/internal/terrario-emitter');

class InputCanceledError extends Error {
constructor(message) {
Expand Down Expand Up @@ -45,9 +46,14 @@ async function entryPoint() {

try {
const parseTimeStart = performance.now();
const result = compile(input);
const ast = parse(input);
const code = emit(ast);
const parseTimeEnd = performance.now();
console.log(result);
console.log('=== AST ===');
console.log(JSON.stringify(ast, null, ' '));
console.log('=== Code ===');
console.log(code);
console.log('============');
const parseTime = (parseTimeEnd - parseTimeStart).toFixed(3);
console.log(`time: ${parseTime}ms`);
}
Expand Down
122 changes: 77 additions & 45 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
export type Success<T> = {
success: true;
value: T;
index: number;
value: T;
};

export type Failure = { success: false };
export type Failure = {
success: false;
index: number;
};

export type Result<T> = Success<T> | Failure;

export type ParserHandler<T> = (input: string, index: number, state: any) => Result<T>
export type ParserHandler<T> = (input: string, index: number, state: any) => Result<T>;

export function success<T>(index: number, value: T): Success<T> {
return {
Expand All @@ -18,8 +20,11 @@ export function success<T>(index: number, value: T): Success<T> {
};
}

export function failure(): Failure {
return { success: false };
export function failure(index: number): Failure {
return {
success: false,
index: index,
};
}

export class Parser<T> {
Expand Down Expand Up @@ -47,7 +52,8 @@ export class Parser<T> {
}

parse(input: string, state: any = {}): Result<T> {
return this.handler(input, 0, state);
const parser = seq([this, eof], 0);
return parser.handler(input, 0, state);
}

map<U>(fn: (value: T) => U): Parser<U> {
Expand All @@ -71,24 +77,10 @@ export class Parser<T> {
});
}

many(min: number): Parser<T[]> {
return new Parser((input, index, state) => {
let result;
let latestIndex = index;
const accum: T[] = [];
while (latestIndex < input.length) {
result = this.handler(input, latestIndex, state);
if (!result.success) {
break;
}
latestIndex = result.index;
accum.push(result.value);
}
if (accum.length < min) {
return failure();
}
return success(latestIndex, accum);
});
many(min: number): Parser<T[]>
many(min: number, terminator: Parser<unknown>): Parser<T[]>
many(min: number, terminator?: Parser<unknown>): Parser<T[]> {
return (terminator != null) ? manyWithout(this, min, terminator) : many(this, min);
}

option(): Parser<T | null> {
Expand All @@ -99,39 +91,73 @@ export class Parser<T> {
}
}

export function str<T extends string>(value: T): Parser<T> {
function many<T>(parser: Parser<T>, min: number): Parser<T[]> {
return new Parser((input, index, state) => {
let result;
let latestIndex = index;
const accum: T[] = [];
while (latestIndex < input.length) {
result = parser.handler(input, latestIndex, state);
if (!result.success) {
break;
}
latestIndex = result.index;
accum.push(result.value);
}
if (accum.length < min) {
return failure(latestIndex);
}
return success(latestIndex, accum);
});
}

function manyWithout<T>(parser: Parser<T>, min: number, terminator: Parser<unknown>): Parser<T[]> {
return many(seq([
notMatch(terminator),
parser,
], 1), min);
}

export function str<T extends string>(value: T): Parser<T>
export function str(pattern: RegExp): Parser<string>
export function str(value: string | RegExp): Parser<string> {
return (typeof value == 'string') ? strWithString(value) : strWithRegExp(value);
}

function strWithString<T extends string>(value: T): Parser<T> {
return new Parser((input, index, _state) => {
if ((input.length - index) < value.length) {
return failure();
return failure(index);
}
if (input.substr(index, value.length) !== value) {
return failure();
return failure(index);
}
return success(index + value.length, value);
});
}

export function regexp(pattern: RegExp): Parser<string> {
function strWithRegExp(pattern: RegExp): Parser<string> {
const re = RegExp(`^(?:${pattern.source})`, pattern.flags);
return new Parser((input, index, _state) => {
const text = input.slice(index);
const result = re.exec(text);
if (result == null) {
return failure();
return failure(index);
}
return success(index + result[0].length, result[0]);
});
}

type SeqResultItem<T> = T extends Parser<infer R> ? R : never;
type SeqResult<T> = T extends [infer Head, ...infer Tail] ? [SeqResultItem<Head>, ...SeqResult<Tail>] : [];
export function seq<T extends Parser<any>[]>(parsers: [...T]): Parser<SeqResult<[...T]>>;
export function seq<T extends Parser<any>[], U extends number>(parsers: [...T], select: U): T[U];

export function seq<T extends Parser<any>[]>(parsers: [...T]): Parser<SeqResult<[...T]>>
export function seq<T extends Parser<any>[], U extends number>(parsers: [...T], select: U): T[U]
export function seq(parsers: Parser<any>[], select?: number) {
return (select == null) ? seqInternal(parsers) : seqInternalWithSelect(parsers, select);
return (select == null) ? seqAll(parsers) : seqSelect(parsers, select);
}

function seqInternal<T extends Parser<any>[]>(parsers: [...T]): Parser<SeqResult<[...T]>> {
function seqAll<T extends Parser<any>[]>(parsers: [...T]): Parser<SeqResult<[...T]>> {
return new Parser((input, index, state) => {
let result;
let latestIndex = index;
Expand All @@ -148,8 +174,8 @@ function seqInternal<T extends Parser<any>[]>(parsers: [...T]): Parser<SeqResult
});
}

function seqInternalWithSelect<T extends Parser<any>[], U extends number>(parsers: [...T], select: U): T[U] {
return seqInternal(parsers).map(values => values[select]);
function seqSelect<T extends Parser<any>[], U extends number>(parsers: [...T], select: U): T[U] {
return seqAll(parsers).map(values => values[select]);
}

export function alt<T extends Parser<unknown>[]>(parsers: T): T[number] {
Expand All @@ -161,7 +187,7 @@ export function alt<T extends Parser<unknown>[]>(parsers: T): T[number] {
return result;
}
}
return failure();
return failure(index);
});
}

Expand Down Expand Up @@ -197,7 +223,7 @@ export function match<T>(parser: Parser<T>): Parser<T> {
const result = parser.handler(input, index, state);
return result.success
? success(index, result.value)
: failure();
: failure(index);
});
}

Expand All @@ -206,15 +232,15 @@ export function notMatch(parser: Parser<unknown>): Parser<null> {
const result = parser.handler(input, index, state);
return !result.success
? success(index, null)
: failure();
: failure(index);
});
}

export function cond(predicate: (state: any) => boolean): Parser<null> {
return new Parser((input, index, state) => {
return predicate(state)
? success(index, null)
: failure();
: failure(index);
});
}

Expand All @@ -223,22 +249,28 @@ export const lf = str('\n');
export const crlf = str('\r\n');
export const newline = alt([crlf, cr, lf]);

export const sof = new Parser((_input, index, _state) => {
return index == 0
? success(index, null)
: failure(index);
});

export const eof = new Parser((input, index, _state) => {
return index >= input.length
? success(index, null)
: failure();
: failure(index);
});

export const char = new Parser((input, index, _state) => {
if ((input.length - index) < 1) {
return failure();
return failure(index);
}
const value = input.charAt(index);
return success(index + 1, value);
});

export const lineBegin = new Parser((input, index, state) => {
if (index === 0) {
if (sof.handler(input, index, state).success) {
return success(index, null);
}
if (cr.handler(input, index - 1, state).success) {
Expand All @@ -247,7 +279,7 @@ export const lineBegin = new Parser((input, index, state) => {
if (lf.handler(input, index - 1, state).success) {
return success(index, null);
}
return failure();
return failure(index);
});

export const lineEnd = match(alt([
Expand All @@ -266,7 +298,7 @@ export function createLanguage<T>(syntaxes: { [K in keyof T]: (r: Record<string,
for (const key of Object.keys(syntaxes)) {
rules[key] = lazy(() => {
const parser = (syntaxes as any)[key](rules);
if (parser == null) {
if (parser == null || !(parser instanceof Parser)) {
throw new Error('syntax must return a parser.');
}
parser.name = key;
Expand Down
7 changes: 6 additions & 1 deletion src/peg/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import * as Parser from './internal/peg-parser';
import * as Emitter from './internal/terrario-emitter';

export function compile(input: string): string {
const rules = Parser.parse(input);
const parseResult = Parser.parse(input);
if (!parseResult.success) {
console.log(JSON.stringify(parseResult));
throw new Error('parsing error');
}
const rules = parseResult.value;
const code = Emitter.emit(rules);
return code;
}
Loading

0 comments on commit b5ddc85

Please sign in to comment.