Giter Site home page Giter Site logo

ts-pegjs's People

Contributors

andrewtelnov avatar ben-voss avatar borgfriend avatar dependabot[bot] avatar fallstar avatar greenkeeper[bot] avatar hellyeah123 avatar hildjj avatar iccicci avatar kamiazya avatar kkshinkai avatar lindner avatar mgissing avatar mo-ba avatar pcardune avatar pjmolina avatar ppodds avatar siefkenj avatar sirpl avatar stifflerus avatar stonecypher avatar zewa666 avatar zm-cttae-archive avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ts-pegjs's Issues

CLI script in node_modules/.bin is broken

The node_modules/.bin/tspegjs script does not start with #!/usr/bin/env node, so running it directly from the shell results in unexpectedly calling the import program with parameters * as fs from "node:fs".

there is not .d.ts file in dist after build source

I used this lib works with typescript, when I import ts-pegjs lib use with 'import tspegjs from "ts-pegjs"', it got an error shows "Cannot find module 'ts-pegjs' or its corresponding type declarations". Then I clone your lib source to build locally and found that not any .d.ts file in dist folder. Is it possible to add some typing files to the lib dist?
截屏2023-05-14 11 20 11

Generated typescript code producing errors

This is for v0.1.14

The generated typescript code is produces the following errors when compiled (parser.ts is the output of pegjs):

parser.ts(49,40): error TS4070: Parameter 'expected' of public static method from exported class has or is using private name 'Expectation'.

parser.ts(140,20): error TS4031: Public property 'expected' of exported class has or is using private name 'Expectation'.

parser.ts(145,42): error TS4063: Parameter 'expected' of constructor from exported class has or is using private name 'Expectation'.

This happens no matter what grammar I specify in my .pegjs file. For example, the errors occur with a trivial grammar such as
trivial = 'x'

The error seems to stem from generated types Expectation, IOtherExpectation, IEndExpectation, IAnyExpectation, IClassExpectation, IClassParts, and ILiteralExpectation not being exported from the generated parser source file. I can fix this manually to remove the errors but ts-pegjs should not be generating them in the first place.

ts-pegjs: 0.1.14
typescript: 2.8.3
pegjs: 0.10.0

Paste customHeaders above "use strict"

I'm currently trying to add // @ts-nocheck header using customHeaders option. It's not working, because it only works at the top of the file and it gets pasted below "use strict".

I want to use noUnusedLocals and noUnusedParameters rules from tsconfig.json but generated files are not conforming to those rules and that's why I was trying to disable checks at all.

The shoemaker's children always go barefoot - where are types?

So a tool for TypeScript generation doesn't provide types for itself.
And hence the parser build script can't be written in TypeScript, the only island of chaos in my kingdom of order.

const parser = peggy.generate("start = ('a' / 'b')+", {
  output: 'source',
  format: 'commonjs',
  plugins: [tspegjs],
  tspegjs: {
     customHeader: '// LALAL',
  },
});

The tspegjs cannot be there according to peggy-s type definitions.

customHeader: everything but the first line gets dropped

If I take the example from the readme

var parser = pegjs.generate("start = ('a' / 'b')+", {
    output: "source",
    format: "commonjs",
    plugins: [tspegjs],
    "tspegjs": {
        "customHeader": "// import lib\nimport { Lib } from 'mylib';"
    }
});

I end up with this header:

// import lib

// Generated by PEG.js v. 0.10.0 (ts-pegjs plugin v. 0.3.1 )

instead of the expected

// import lib
import { Lib } from 'mylib';

// Generated by PEG.js v. 0.10.0 (ts-pegjs plugin v. 0.3.1 )

docs: exception handler

I'm not sure this works as intended. I get a TypeScript error from the exception handler. The goal in that handler is being able to call format() with type safety.

import { PeggySyntaxError, parse } from './arithmetics';

try {
    const sampleOutput = parse('my sample...');
} catch (ex: PeggySyntaxError) {
    // Handle parsing error
    // [...]
}

Playground Website

I notice there is no playground website for ts-pegjs. It would be great to have so people could play around with ts-pegjs to decide if it's the tool they need.

Is this repository the appropriate place for a playground? If so, there are a couple of options.

  1. Change the repo to use npm workspaces. The typical thing to do would be to add a projects/ts-pegjs and projects/ts-pegjs-playground. Via some tsconfig.json magic, one can configure import ... "ts-pegjs" to import the local source instead of a global package. There would then be a package.json in the projects/ts-pegjs directory and one in the root directory marked "private": true. Only the projects/ts-pegjs workspace would be published on npm.
  2. Add a playground directory and import via relative path ../src/... the necessary components.
  3. Create a separate repository.

Implicit any

I find the rule noImplicitAny useful, but I had to turn it off for this, because this produces implict anys.

Could those anys be made explicit please, so that I can turn that rule back on? ❤️

This tool is possibly awesome. I'm brand new

Invalid code generation for "pluck"

The list example from Peggy does not work as intended:

list = head:word tail:(_ "," _ @word)* { return [head, ...tail]; }
word = $[a-z]i+
_ = [ \t]*

If invoked via tspegjs, there is no code difference in the TypeScript output if I change @word to just word in the first rule. On the other hand, if I invoke peggy (1.2.0) directly, the generated code (JavaScript) differs as expected:

727c728,729
<             s3 = s7;
---
>             s4 = [s4, s5, s6, s7];
>             s3 = s4;

Errors when format set to "es"

Just tried generating the parser using the es format and I get the following errors. Thanks for any help!

Cannot redeclare exported variable 'SyntaxError'.
  > export class SyntaxError extends Error {
Cannot find name 'IParseOptions'.
  > function peg$parse(input: string, options?: IParseOptions) {
Cannot redeclare exported variable 'SyntaxError'.
  > export {
  >   SyntaxError as SyntaxError,
  >   peg$parse as parse
  > };

tspegjs options get ignored

The field tspegjs is getting ignored currently, my options are:

const output = peggy.generate(grammar, {
  output: "source",
  format: "commonjs",
  cache: true,
  plugins: [tspegjs],
  tspegjs: {
    customHeader: "// import lib\nimport { Lib } from 'mylib';",
  },
});

But no header is generated on the output.

optimize: size produces invalid typescript

I believe this was the cause of #12, but wanted to open another issue as that one is closed and almost a year old.

If I clone this repo and include --optimize size in the test:cli script, I get:

> pegjs --plugin ./src/tspegjs --extra-options-file test/genoptions2.json  --optimize size --allowed-start-rules groupFile,templateFile,templateFileRaw,templateAndEOF -o output/st2.ts examples/st.pegjs && tsc output/st2.ts && tslint output/st2.ts

output/st2.ts(938,5): error TS2540: Cannot assign to 'peg$startRuleIndex' because it is a constant or a read-only property.
output/st2.ts(1067,5): error TS2322: Type 'number[]' is not assignable to type 'number'.
output/st2.ts(1074,18): error TS2339: Property 'length' does not exist on type 'number'.
output/st2.ts(1220,66): error TS2339: Property 'length' does not exist on type 'string | RegExp | ILiteralExpectation | IClassExpectation | IAnyExpectation | IOtherExpectation |...'.
  Property 'length' does not exist on type 'RegExp'.
output/st2.ts(1234,66): error TS2339: Property 'length' does not exist on type 'string | RegExp | ILiteralExpectation | IClassExpectation | IAnyExpectation | IOtherExpectation |...'.
  Property 'length' does not exist on type 'RegExp'.
output/st2.ts(1248,40): error TS2339: Property 'test' does not exist on type 'string | RegExp | ILiteralExpectation | IClassExpectation | IAnyExpectation | IOtherExpectation |...'.
  Property 'test' does not exist on type 'string'.
output/st2.ts(1266,51): error TS2339: Property 'length' does not exist on type 'string | RegExp | ILiteralExpectation | IClassExpectation | IAnyExpectation | IOtherExpectation |...'.
  Property 'length' does not exist on type 'RegExp'.
output/st2.ts(1273,24): error TS2345: Argument of type 'string | RegExp | ILiteralExpectation | IClassExpectation | IAnyExpectation | IOtherExpectation |...' is not assignable to parameter of type 'Expectation'.
  Type 'string' is not assignable to type 'Expectation'.
output/st2.ts(1289,25): error TS2339: Property 'slice' does not exist on type 'number'.
output/st2.ts(1295,38): error TS2339: Property 'apply' does not exist on type 'string | RegExp | ILiteralExpectation | IClassExpectation | IAnyExpectation | IOtherExpectation |...'.
  Property 'apply' does not exist on type 'string'.

I am currently converting my library to typescript, and this library has been tremendously helpful, but I had to remove optimize: size, which I believe is the main contributor to the doubling in size of the bundle unminified, and 25% increase minified (its about an order of magnitude larger) minified. Bundles aren't necessarily the main way the library will be used going forward, but it's hard to fight for a size increase.

Slow compile times

I have two relatively simple grammars. But the problem is that if I generate TypeScript code (as described in the README), the TypeScript compiler takes forever to compile the generated code. Is there a setting or annotation that helps with the compilation times? As it is, it is pretty much unusable for me.

Unused functions in generated source

Hi,

it seems that this generator adds some unused functions to parser source and my TS compiler is shouting on me.

  function location(): IFileRange {
    return peg$computeLocation(peg$savedPos, peg$currPos);
  }

  function expected(description: string, location1?: IFileRange) {
    location1 = location1 !== undefined
      ? location1
      : peg$computeLocation(peg$savedPos, peg$currPos);

    throw peg$buildStructuredError(
      [peg$otherExpectation(description)],
      input.substring(peg$savedPos, peg$currPos),
      location1
    );
  }

  function error(message: string, location1?: IFileRange) {
    location1 = location1 !== undefined
      ? location1
      : peg$computeLocation(peg$savedPos, peg$currPos);

    throw peg$buildSimpleError(message, location1);
  }

  function peg$anyExpectation(): IAnyExpectation {
    return { type: "any" };
  }

Support Peggy 3.0.1

In particular, I'd like to be able to use the new |..| repetition syntax.

Options documentation is confusing.

First in the readme some options are named with dashes and others aren't, not sure why?

Also I was trying to get returnTypes working and since there's no examples and it was grouped with the plugin options that went under the tspegjs property, I assumed it would go there. Had to search the code to find out what was going on.

A example in the readme using all the options possible would be nice.

Additionally I can't get returnTypes to work. I mean I can see it's typing all the rule functions, but since peg$parsestart is wrapped by peg$startRuleFunction and peg$parse is further typed as ParseFunction, both returning any, even if I set the start rule type, the type of parse is always any. Is this intended? Should I open an issue?

Solution redesign using AST transform

Currently the repository is a fork of pegjs with many hardcoded changes that are difficult to maintain.

It was generally suggested as a collateral of #92 that we transform the parser outpu tree instead.

I've been looking into doing a PR to add Peggy 3.0 support, but so far I have mostly learned that the code generation in Peggy has changed quite a lot since 2.0. So much so, that it's probably simpler to do a complete rewrite of ts-pegs than to attempt merging in changes from Peggy 3.0.

@pjmolina

It appears that you duplicate most of the bytecode generation from Peggy. Is there a reason you don't just strip the typescript annotations from each Peggy action, let Peggy generate the source, and then re-export the Peggy-generated parser with types?

@siefkenj

I've been looking into doing a PR to add Peggy 3.0 support, but so far I have mostly learned that the code generation in Peggy has changed quite a lot since 2.0. So much so, that it's probably simpler to do a complete rewrite of ts-pegs than to attempt merging in changes from Peggy 3.0.

@nene

Is there a way to convert the pegjs output to an estree & add types to that, then convert back to source?

Should be possible with recast or jscodeshift.

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

ParserFunction type does not specify options as optional

On line 1322 you have

export type ParseFunction = (input: string, options: IParseOptions) => any

while on line 1004 you have

function peg$parse(input: string, options?: IParseOptions)

Of course, this causes an error when you try to call parse() using only the string argument. The quick work-around currently is to modify the output .ts file to export the correct type, or to call the parse function with an empty object for options, but both of these are undesirable in the long run. Is this important enough to merit a patch?

feature: pass custom tslint ignores

finally a plugin to create typescript results out of pegs 🎉

It'd be great if we could pass custom tslintIgnoreRules via the CLI to suite whatever settings the consumer has.

So here https://github.com/metadevpro/ts-pegjs/blob/master/src/passes/generate-ts.js#L1233
we could instead do something like:

function generateGeneratedByComment() {
      return [
        "// Generated by PEG.js v. " + pegJsVersion + " (ts-pegjs plugin v. " + pluginVersion + " )",
        "//",
        "// https://pegjs.org/   https://github.com/metadevpro/ts-pegjs",
        "",
        ...options.tslintIgnores
          ? options.tslintIgnores.map((rule) => "// tslint:disable:" + rule)
          : [
            "// tslint:disable:only-arrow-functions",
            "// tslint:disable:object-literal-shorthand",
            "// tslint:disable:trailing-comma",
            "// tslint:disable:object-literal-sort-keys",
            "// tslint:disable:one-variable-per-declaration",
            "// tslint:disable:max-line-length",
            "// tslint:disable:no-consecutive-blank-lines",
            "// tslint:disable:align"
          ],
        options.trace ? "// tslint:disable:no-console" : ""
      ].join("\n");
    }

to either keep your locals or provide an array of rules (eg string separated) via the CLI.

I wasn't sure though how to pass new cli attributes, as even the mentioned example with --cache results in a Too many arguments. error

--allowedStartRules doesn't work with the CLI

I think the underlying code expects an array of strings, but the arg parsing just assigns the comma separated string to opts. Doesn't it need to be split in cli.js at some point, either when parsing the arg or later when assigning to opts?

allowedStartRules = args[index + 1].split(',');

or

opts.allowedStartRules = allowedStartRules.split(',');

Option to get object instead of code string

Hi,
I thought it could be a good idea to be able to get the parser object (like with normal pegjs) instead of getting the code, putting the code in a file, then importing that file.

Produce typed output from the parser

It would be nice if my typescript parser had typescript annotations on what it produced.

Consider the following toy parser example.

Dumb bad code

Doc
  = str
  / num;
  
str
  = 'bob';
  
num
  = '2' { return 2; }
  / '3' { return 3; };

This allows three legal documents. It would be useful to be able to tell the plugin "announce that the result of this parse will be number | string."

Obviously in this case this is silly, but for something that returns highly typed content, that would actually be pretty powerful.

Allow specifying return types directly in the peg source

Maintaining the list of output types is a chore and error-prone.

I would like to propose some enhancements to make this easier.

  1. Allow for adding a Return type of explicit code in action node types. A simple example would look like this:
Integer "integer"
  = _ [0-9]+ { 
     return parseInt(text(), 10);
     // @returns number
 }

This would replace the equivalent entry of {'Integer', 'number'} in the options.

  1. For a node of type 'choice' return a union type if types for all choices are defined. For example Foo = A / B / C would have a return of TypeA | TypeB | TypeC if all choices are typed.

  2. For type 'sequence' return a string type.

--allowed-start-rules cli option is broken.

Trying to use the --allowed-start-rules option results in an exception:

[dimo@bender tmp]$ tspegjs --allowed-start-rules Expression,Term tmp.pegjs 
/home/dimo/tmp/tmp/node_modules/ts-pegjs/src/passes/generate-ts.js:1026
        options.allowedStartRules.map(
                                  ^

TypeError: options.allowedStartRules.map is not a function
    at generateToplevel (/home/dimo/tmp/tmp/node_modules/ts-pegjs/src/passes/generate-ts.js:1026:35)
    at generateTS (/home/dimo/tmp/tmp/node_modules/ts-pegjs/src/passes/generate-ts.js:1534:30)
    at /home/dimo/tmp/tmp/node_modules/pegjs/lib/compiler/index.js:62:50
    at Object.each (/home/dimo/tmp/tmp/node_modules/pegjs/lib/utils/arrays.js:63:7)
    at Object.compile (/home/dimo/tmp/tmp/node_modules/pegjs/lib/compiler/index.js:62:16)
    at Object.generate (/home/dimo/tmp/tmp/node_modules/pegjs/lib/peg.js:50:25)
    at /home/dimo/tmp/tmp/node_modules/ts-pegjs/src/cli.js:82:24
    at FSReqCallback.readFileAfterClose [as oncomplete] (node:internal/fs/read_file_context:63:3)

I believe options.allowedStartRules is a string, while an array is expected.

Perhaps there is a missing split(',') on this line https://github.com/metadevpro/ts-pegjs/blob/master/src/cli.js#L32

Release 0.2.6

I'd appreciate a version bump so I can incorporate my PR #38 into our systems.

Any ETA? Anything I can do to help?

Thanks!

Have example outputs in "examples" folder

I think it would be great if there is an example output in either the examples folder or the README.md to quickly allow us know what the differences are made by ts-pegjs as compared to the vanilla pegjs.

Thanks

feature: top-level imports

recently I've been working on a pegfile which's result will need to import other classes. In JS I've simply used to use require to import the dependency when needed. Now over at TS that would have to be pulled up to the top of the generated file since imports are only allowed in modules.

Now looking at how you generate the topLevelInfo it would be nice if we could provide our custom stuff, which should be printed before that. This could be achieved by providing another CLI attribute - e.g. customTopLevelInfo.

I'm not sure whether pegjs provides any means for that by itself. The initializer would be too late

automated test to verify typescript compiler builds of resulting TS file

with the latest release and combination with a newer TypeScript version, there was a new issue introduced (see PR #17). It would be great if we could add an integration test to make sure this type of issues is getting caught.

Besides that, changing return types of functions is considered a breaking-change so the latest release really should have been bumped with 0.2.0 due to non RC policy with SemVer instead of a major bump

Incompatible with latest release of Peggy.js

Peggy.js just released a breaking release which amongst other things changes how allowedStartRules are defined peggyjs/peggy#175
As a result, running node src/generate-parser.js with the ts-pegjs plugin now causes the error below to occur

/path-to-project/node_modules/ts-pegjs/src/passes/generate-ts.js:1076
        options.allowedStartRules.map(
                                  ^

TypeError: Cannot read properties of undefined (reading 'map')
    at generateToplevel (/path-to-project/node_modules/ts-pegjs/src/passes/generate-ts.js:1076:35)
    at generateTS (/path-to-project/node_modules/ts-pegjs/src/passes/generate-ts.js:1562:30)
    at /node_modules/peggy/lib/compiler/index.js:99:9
    at Array.forEach (<anonymous>)
    at /node_modules/peggy/lib/compiler/index.js:96:21
    at Array.forEach (<anonymous>)
    at Object.compile (/node_modules/peggy/lib/compiler/index.js:92:25)
    at Object.generate /node_modules/peggy/lib/peg.js:112:25)
    at /expression-parser/src/generate-parser.js:7:22
    at FSReqCallback.readFileAfterClose [as oncomplete] (node:internal/fs/read_file_context:68:3)

minimal reproducible config

// generate-parser.js
var fs = require('fs');
var peggy = require('peggy');
var tspegjs = require('ts-pegjs');

fs.readFile('examples/arithmetics.pegjs', function (err, data) {
  if (err) throw err;
  var parser = peggy.generate(data.toString(), {
    output: 'source',
    cache: true,
    plugins: [tspegjs],
    tspegjs: {
      customHeader: '// @ts-nocheck',
    },
  });
  fs.writeFileSync('examples/arithmetics.ts', parser);
});

Actions of expressions are always of type any no matter what is specified in returnTypes

The returnTypes option is ignored for expressions with actions. Currently I do think all actions always have a return type of any no matter what is being specified in returnTypes. This is important to me because I would like to make the parser generated with plantuml-parser internally typed.

To illustrate what is going on consider the following very simple rule:

Line = .*
{
  return "A string!"
}

when compiling a parser using this rule and the following returnTypes declaration:

returnTypes: {
  'Line': 'number'
}

I would expect the type script compilation to fail. Because the return type of Line should be number but is always string.

Full blown example

Consider the following file:

// genparser.js
const pegjs = require('pegjs');
const tspegjs = require('ts-pegjs');

let parser = pegjs.generate(`
    Line = .*
      {
        return "A string!"
      }
  `,
  {
    format: 'commonjs',
    output: 'source',
    plugins: [tspegjs],
    trace: true,
    tspegjs: {
      customHeader: `
        // Hacky way to make the generated
        // parser executable.
        // This is only here for the sake of
        // this demonstration
        const Tracer = require('pegjs-backtrace');

        const parserInput = '';
        console.log(
          peg$parse(
            parserInput,
            {
              tracer: new Tracer(
                parserInput,
                {
                  showTrace: true,
                  useColor: false,
                }
              )
            }
          )
        )
      `
    },
    returnTypes: {
      'Line': 'number'
    }
  }
)
console.log(parser)

generating parser.ts

$ npm install pegjs ts-pegjs pegjs-backtrace'
$ node genparser.js > parser.ts

Give me the following

`parser.ts`

// tslint:disable:only-arrow-functions
// tslint:disable:object-literal-shorthand
// tslint:disable:trailing-comma
// tslint:disable:object-literal-sort-keys
// tslint:disable:one-variable-per-declaration
// tslint:disable:max-line-length
// tslint:disable:no-consecutive-blank-lines
// tslint:disable:align
// tslint:disable:no-console

// Generated by PEG.js v. 0.10.0 (ts-pegjs plugin v. 0.2.6 )
//
// https://pegjs.org/   https://github.com/metadevpro/ts-pegjs

"use strict";


        // Hacky way to make the generated
        // parser executable.
        // This is only here for the sake of
        // this demonstration
        const Tracer = require('pegjs-backtrace');

        const parserInput = '';
        console.log(
          peg$parse(
            parserInput,
            {
              tracer: new Tracer(
                parserInput,
                {
                  showTrace: true,
                  useColor: false,
                }
              )
            }
          )
        )
      
export interface IFilePosition {
  offset: number;
  line: number;
  column: number;
}

export interface IFileRange {
  start: IFilePosition;
  end: IFilePosition;
}

export interface ILiteralExpectation {
  type: "literal";
  text: string;
  ignoreCase: boolean;
}

export interface IClassParts extends Array<string | IClassParts> {}

export interface IClassExpectation {
  type: "class";
  parts: IClassParts;
  inverted: boolean;
  ignoreCase: boolean;
}

export interface IAnyExpectation {
  type: "any";
}

export interface IEndExpectation {
  type: "end";
}

export interface IOtherExpectation {
  type: "other";
  description: string;
}

export type Expectation = ILiteralExpectation | IClassExpectation | IAnyExpectation | IEndExpectation | IOtherExpectation;

export class SyntaxError extends Error {
  public static buildMessage(expected: Expectation[], found: string | null) {
    function hex(ch: string): string {
      return ch.charCodeAt(0).toString(16).toUpperCase();
    }

    function literalEscape(s: string): string {
      return s
        .replace(/\\/g, "\\\\")
        .replace(/"/g,  "\\\"")
        .replace(/\0/g, "\\0")
        .replace(/\t/g, "\\t")
        .replace(/\n/g, "\\n")
        .replace(/\r/g, "\\r")
        .replace(/[\x00-\x0F]/g,            (ch) => "\\x0" + hex(ch) )
        .replace(/[\x10-\x1F\x7F-\x9F]/g, (ch) => "\\x"  + hex(ch) );
    }

    function classEscape(s: string): string {
      return s
        .replace(/\\/g, "\\\\")
        .replace(/\]/g, "\\]")
        .replace(/\^/g, "\\^")
        .replace(/-/g,  "\\-")
        .replace(/\0/g, "\\0")
        .replace(/\t/g, "\\t")
        .replace(/\n/g, "\\n")
        .replace(/\r/g, "\\r")
        .replace(/[\x00-\x0F]/g,            (ch) => "\\x0" + hex(ch) )
        .replace(/[\x10-\x1F\x7F-\x9F]/g, (ch) => "\\x"  + hex(ch) );
    }

    function describeExpectation(expectation: Expectation) {
      switch (expectation.type) {
        case "literal":
          return "\"" + literalEscape(expectation.text) + "\"";
        case "class":
          const escapedParts = expectation.parts.map((part) => {
            return Array.isArray(part)
              ? classEscape(part[0] as string) + "-" + classEscape(part[1] as string)
              : classEscape(part);
          });

          return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]";
        case "any":
          return "any character";
        case "end":
          return "end of input";
        case "other":
          return expectation.description;
      }
    }

    function describeExpected(expected1: Expectation[]) {
      const descriptions = expected1.map(describeExpectation);
      let i: number;
      let j: number;

      descriptions.sort();

      if (descriptions.length > 0) {
        for (i = 1, j = 1; i < descriptions.length; i++) {
          if (descriptions[i - 1] !== descriptions[i]) {
            descriptions[j] = descriptions[i];
            j++;
          }
        }
        descriptions.length = j;
      }

      switch (descriptions.length) {
        case 1:
          return descriptions[0];

        case 2:
          return descriptions[0] + " or " + descriptions[1];

        default:
          return descriptions.slice(0, -1).join(", ")
            + ", or "
            + descriptions[descriptions.length - 1];
      }
    }

    function describeFound(found1: string | null) {
      return found1 ? "\"" + literalEscape(found1) + "\"" : "end of input";
    }

    return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
  }

  public message: string;
  public expected: Expectation[];
  public found: string | null;
  public location: IFileRange;
  public name: string;

  constructor(message: string, expected: Expectation[], found: string | null, location: IFileRange) {
    super();
    this.message = message;
    this.expected = expected;
    this.found = found;
    this.location = location;
    this.name = "SyntaxError";

    if (typeof (Error as any).captureStackTrace === "function") {
      (Error as any).captureStackTrace(this, SyntaxError);
    }
  }
}

export interface ITraceEvent {
  type: string;
  rule: string;
  result?: any;
  location: IFileRange;
}

export class DefaultTracer {
  private indentLevel: number;

  constructor() {
    this.indentLevel = 0;
  }

  public trace(event: ITraceEvent) {
    const that = this;

    function log(evt: ITraceEvent) {
      function repeat(text: string, n: number) {
         let result = "", i;

         for (i = 0; i < n; i++) {
           result += text;
         }

         return result;
      }

      function pad(text: string, length: number) {
        return text + repeat(" ", length - text.length);
      }

      if (typeof console === "object") {
        console.log(
          evt.location.start.line + ":" + evt.location.start.column + "-"
            + evt.location.end.line + ":" + evt.location.end.column + " "
            + pad(evt.type, 10) + " "
            + repeat("  ", that.indentLevel) + evt.rule
        );
      }
    }

    switch (event.type) {
      case "rule.enter":
        log(event);
        this.indentLevel++;
        break;

      case "rule.match":
        this.indentLevel--;
        log(event);
        break;

      case "rule.fail":
        this.indentLevel--;
        log(event);
        break;

      default:
        throw new Error("Invalid event type: " + event.type + ".");
    }
  }
}

function peg$parse(input: string, options?: IParseOptions) {
  options = options !== undefined ? options : {};

  const peg$FAILED: Readonly<{}> = {};

  const peg$startRuleFunctions: {[id: string]: any} = { Line: peg$parseLine };
  let peg$startRuleFunction: () => any = peg$parseLine;

  const peg$c0 = peg$anyExpectation();
  const peg$c1 = function(): any {
          return "A string!"
        };

  let peg$currPos = 0;
  let peg$savedPos = 0;
  const peg$posDetailsCache = [{ line: 1, column: 1 }];
  let peg$maxFailPos = 0;
  let peg$maxFailExpected: Expectation[] = [];
  let peg$silentFails = 0;

  const peg$tracer = "tracer" in options ? options.tracer : new DefaultTracer();

  let peg$result;

  if (options.startRule !== undefined) {
    if (!(options.startRule in peg$startRuleFunctions)) {
      throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
    }

    peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
  }

  function text(): string {
    return input.substring(peg$savedPos, peg$currPos);
  }

  function location(): IFileRange {
    return peg$computeLocation(peg$savedPos, peg$currPos);
  }

  function expected(description: string, location1?: IFileRange) {
    location1 = location1 !== undefined
      ? location1
      : peg$computeLocation(peg$savedPos, peg$currPos);

    throw peg$buildStructuredError(
      [peg$otherExpectation(description)],
      input.substring(peg$savedPos, peg$currPos),
      location1
    );
  }

  function error(message: string, location1?: IFileRange) {
    location1 = location1 !== undefined
      ? location1
      : peg$computeLocation(peg$savedPos, peg$currPos);

    throw peg$buildSimpleError(message, location1);
  }

  function peg$literalExpectation(text1: string, ignoreCase: boolean): ILiteralExpectation {
    return { type: "literal", text: text1, ignoreCase: ignoreCase };
  }

  function peg$classExpectation(parts: IClassParts, inverted: boolean, ignoreCase: boolean): IClassExpectation {
    return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase };
  }

  function peg$anyExpectation(): IAnyExpectation {
    return { type: "any" };
  }

  function peg$endExpectation(): IEndExpectation {
    return { type: "end" };
  }

  function peg$otherExpectation(description: string): IOtherExpectation {
    return { type: "other", description: description };
  }

  function peg$computePosDetails(pos: number) {
    let details = peg$posDetailsCache[pos];
    let p;

    if (details) {
      return details;
    } else {
      p = pos - 1;
      while (!peg$posDetailsCache[p]) {
        p--;
      }

      details = peg$posDetailsCache[p];
      details = {
        line: details.line,
        column: details.column
      };

      while (p < pos) {
        if (input.charCodeAt(p) === 10) {
          details.line++;
          details.column = 1;
        } else {
          details.column++;
        }

        p++;
      }

      peg$posDetailsCache[pos] = details;

      return details;
    }
  }

  function peg$computeLocation(startPos: number, endPos: number): IFileRange {
    const startPosDetails = peg$computePosDetails(startPos);
    const endPosDetails = peg$computePosDetails(endPos);

    return {
      start: {
        offset: startPos,
        line: startPosDetails.line,
        column: startPosDetails.column
      },
      end: {
        offset: endPos,
        line: endPosDetails.line,
        column: endPosDetails.column
      }
    };
  }

  function peg$fail(expected1: Expectation) {
    if (peg$currPos < peg$maxFailPos) { return; }

    if (peg$currPos > peg$maxFailPos) {
      peg$maxFailPos = peg$currPos;
      peg$maxFailExpected = [];
    }

    peg$maxFailExpected.push(expected1);
  }

  function peg$buildSimpleError(message: string, location1: IFileRange) {
    return new SyntaxError(message, [], "", location1);
  }

  function peg$buildStructuredError(expected1: Expectation[], found: string | null, location1: IFileRange) {
    return new SyntaxError(
      SyntaxError.buildMessage(expected1, found),
      expected1,
      found,
      location1
    );
  }

  function peg$parseLine(): number {
    const startPos = peg$currPos;
    let s0, s1, s2;

    peg$tracer.trace({
      type: "rule.enter",
      rule: "Line",
      location: peg$computeLocation(startPos, startPos)
    });

    s0 = peg$currPos;
    s1 = [];
    if (input.length > peg$currPos) {
      s2 = input.charAt(peg$currPos);
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) { peg$fail(peg$c0); }
    }
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      if (input.length > peg$currPos) {
        s2 = input.charAt(peg$currPos);
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) { peg$fail(peg$c0); }
      }
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$c1();
    }
    s0 = s1;

    if (s0 !== peg$FAILED) {
      peg$tracer.trace({
        type: "rule.match",
        rule: "Line",
        result: s0,
        location: peg$computeLocation(startPos, peg$currPos)
      });
    } else {
      peg$tracer.trace({
        type: "rule.fail",
        rule: "Line",
        location: peg$computeLocation(startPos, startPos)
      });
    }

    return s0;
  }

  peg$result = peg$startRuleFunction();

  if (peg$result !== peg$FAILED && peg$currPos === input.length) {
    return peg$result;
  } else {
    if (peg$result !== peg$FAILED && peg$currPos < input.length) {
      peg$fail(peg$endExpectation());
    }

    throw peg$buildStructuredError(
      peg$maxFailExpected,
      peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
      peg$maxFailPos < input.length
        ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
        : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
    );
  }
}

export interface IParseOptions {
  filename?: string;
  startRule?: string;
  tracer?: any;
  [key: string]: any;
}
export type ParseFunction = (input: string, options?: IParseOptions) => any;
export const parse: ParseFunction = peg$parse;

Then, compilation works just fine:

$ npx tsc parser.ts # this should fail

and even running the parser

$ node parser.js
ENTER #1 1:1-1:1 Line
  
  ^
MATCH #1 1:1-1:1 Line
  
  ^
A string!

Looking at the generated typescript output (parser.ts). I do think the following lines:

  const peg$c1 = function(): any {
          return "A string!"
        };

should be:

  const peg$c1 = function(): number {
          return "A string!"
        };

If I change this manually, I get the expected typescript compilation error:

parser.ts:266:11 - error TS2322: Type '"A string!"' is not assignable to type 'number'.

266           return "A string!"
              ~~~~~~~~~~~~~~~~~~

Alternative of strings returns Readonly<any>

Minimal example of a problem:

PEGjs code:

START
  = "a"
  / "b"

Types definition:

{
  "returnTypes": {
    "START": "string"
  }
}

Example above, run in --cache mode, produces invalid TypeScript code. peg$parseSTART's function type should be string (as declared in returnTypes) because the only valid return of START rule is a string.
However if neither "a" nor "b" is found (parsed), peg$FAILED is returned which type is Readonly<any>. In the result the return type of peg$parseSTART function is string|Readonly<any>.

Wrong documentation

Hi,

your documentation says to use tspegjs as a pegjs plugin like
pegjs --plugin ./src/tspegjs -o examples/arithmetics.ts --cache examples/arithmetics.pegjs

But looking at your sources show why it doesn't work. In fact, tspegjs does the job
../node_modules/.bin/tspegjs -o parser.ts --cache ./arithmetics..pegjs

Did I missed something ?
Thanks

Cannot assign to 'peg$startRuleIndex' because it is a constant or a read-only property.

src/ts/fslp.js → build/fslp.js...
[!] (rpt2 plugin) Error: C:/Users/john/projects/fslp/build/gen/parser.ts(189,5): semantic error TS2540 Cannot assign to 'peg$startRuleIndex' because it is a constant or a read-only property.
build\gen\parser.ts
Error: C:/Users/john/projects/fslp/build/gen/parser.ts(189,5): semantic error TS2540 Cannot assign to 'peg$startRuleIndex' because it is a constant or a read-only property.
    at error (C:\Users\john\projects\fslp\node_modules\rollup\dist\rollup.js:170:15)
    at C:\Users\john\projects\fslp\node_modules\rollup\dist\rollup.js:17413:17
    at <anonymous>

IParseOptions contains unused filename field, but is missing the grammarSource field

I was attempting to pass the name of the parsed file to the generated parser. Because the IParseOptions interface contains filename field, I assumed that's the one to use... took me a while to figure out why it doesn't work.

Turns out that instead I should have used the grammarSource field: https://peggyjs.org/documentation.html#generating-a-parser-javascript-api

Related to this, the SyntaxError.format() method has the following interface:

format(sources: { source: string; text: string }[]): string

This creates a problem when there is no filename, which happens when grammarSource option was not specified. At the moment I'm forced to do the curse undefined into a string to make it work:

error.format([{
  source: undefined as unknown as string,
  text: "full source code...",
}])

Generated code doesn't compile with 'noImplicitAny'

Parsers that are generated with the "returnTypes" option do not type check when 'noImplicitAny' option is turned on.

For example, running tsc on the generated 'arithmetics-typed.ts' example produces the following:

▶ npx tsc --noImplicitAny output/arithmetics-typed.ts   
output/arithmetics-typed.ts(940,5): error TS2322: Type '{}' is not assignable to type 'number'.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.