metadevpro / ts-pegjs Goto Github PK
View Code? Open in Web Editor NEWPlugin for pegjs to generate TypeScript parsers.
License: MIT License
Plugin for pegjs to generate TypeScript parsers.
License: MIT License
I have seen that i can define return types of rules.
Is there also a way to define the return type of the parse-function itself?
Fwik the TypeScript community now sees the I
prefix as an antipattern and is moving away from it.
microsoft/TypeScript-Handbook#121
I would suggest Peg$
prefix for some similarity to the original PEG.js JavaScript output!
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"
.
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?
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
I'm trying to use my own composite types as output types for the parser. I don't see my use-case in the README. Is this supported? Thanks!
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.
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.
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 )
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
// [...]
}
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.
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.playground
directory and import via relative path ../src/...
the necessary components.I find the rule noImplicitAny
useful, but I had to turn it off for this, because this produces implict any
s.
Could those any
s be made explicit please, so that I can turn that rule back on? ❤️
This tool is possibly awesome. I'm brand new
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;
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
> };
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.
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.
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.
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" };
}
The current version of peggy will generate a SyntaxError class that has a format() method for displaying a more informative error message. This is described in the documentation here, and implemented in the peggy source here: lib/compiler/passes/generate-js.js
It would be great to have this feature built into ts-pegjs as well! Should I try submitting a pull request for this?
Peggy supports --dependencies
and --dependency
per documentation.
Would like to have these evaluated as an import
.
Would be beneficial for TypeScript, Webpack and many other dependencies.
It's possible to implement this by a text replacement. The output looks like let matchers = require('matchers');
In particular, I'd like to be able to use the new |..|
repetition syntax.
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?
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.
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?
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.
To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:
.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.
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 🤖
There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.
Your Greenkeeper Bot 🌴
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?
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
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(',');
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.
Hello. Want to inform that PEG.js is no longer maintained: pegjs/pegjs#639. There is a compatible alternative (pegjs/pegjs#667) called Peggy, that is revived version of original project. Would you consider to maintain compatibility with successor?
It appears that pegjs changed their API for compiler passes
// pegjs 0.10 api pass(ast, options)
// pegjs 0.11+ api pass(ast, config, options);
It should be fairly easy to special case that.
It would be nice if my typescript parser had typescript annotations on what it produced.
Consider the following toy parser example.
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.
--extra-options-file
is super useful for a pegconfig.json
. Can we have that in the ts-pegjs CLI?
Maintaining the list of output types is a chore and error-prone.
I would like to propose some enhancements to make this easier.
Integer "integer"
= _ [0-9]+ {
return parseInt(text(), 10);
// @returns number
}
This would replace the equivalent entry of {'Integer', 'number'}
in the options.
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.
For type 'sequence' return a string type.
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
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!
I have been working on a project that automatically generates typescript types from a Peggy grammar.
Source is here: https://github.com/siefkenj/peggy-to-ts
Playground is here: https://siefkenj.github.io/peggy-to-ts
If you think this would be good to integrate into ts-pegjs
, I would be happy to make a PR (but I'd need some guidance about exactly how to integrate it with ts-pegjs
, since Peggy doesn't normally produce multiple output files...).
currently, peggy is added as a peer dep and a normal production dep. the production dep should be moved to a dev dep.
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
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
Readme example uses a file that doesn't exist. Suggest either update readme or include examples/arithmetics.ts
in repo.
Thanks for the great work!
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
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);
});
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
.
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
// 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!"
~~~~~~~~~~~~~~~~~~
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>
.
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
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>
ts-pegjs/src/passes/constants.ts
Line 56 in 53e09b6
Should be:
format(sources: {
source: any;
text: string;
}[]): string;
If you want to change source -> grammarSource, you should change the implementation of format, and note in the docs that this is different than what peggy normally uses.
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...",
}])
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'.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.