From bbe9864015370d4323d71d934ab9fcbf06ff3cad Mon Sep 17 00:00:00 2001 From: Joshua Worth Date: Sat, 13 Jan 2018 10:59:05 +0100 Subject: [PATCH 1/3] Generated parser compiles with TypeScript's strict mode --- src/passes/generate-bytecode-ts.js | 2 +- src/passes/generate-ts.js | 136 ++++++++++++++++++----------- 2 files changed, 84 insertions(+), 54 deletions(-) diff --git a/src/passes/generate-bytecode-ts.js b/src/passes/generate-bytecode-ts.js index 1718ca9..69ef680 100644 --- a/src/passes/generate-bytecode-ts.js +++ b/src/passes/generate-bytecode-ts.js @@ -200,7 +200,7 @@ function generateBytecode(ast) { function addFunctionConst(params, code) { return addConst( - "function(" + params.join(", ") + ") {" + code + "}" + "function(" + params.map(v => v + ": any").join(", ") + ") {" + code + "}" ); } diff --git a/src/passes/generate-ts.js b/src/passes/generate-ts.js index 91d0e9f..3089c36 100644 --- a/src/passes/generate-ts.js +++ b/src/passes/generate-ts.js @@ -730,7 +730,7 @@ function generateTS(ast, options) { code = compile(rule.bytecode); - parts.push("function peg$parse" + rule.name + "() {"); + parts.push("function peg$parse" + rule.name + "(): any {"); if (options.trace) { parts.push(" const startPos = peg$currPos;"); @@ -764,36 +764,49 @@ function generateTS(ast, options) { parts.push(options.tspegjs.customHeader); } parts.push([ - "export class SyntaxError extends Error {", - " public static buildMessage(expected: string, found: string) {", - " const DESCRIBE_EXPECTATION_FNS = {", - " literal(expectation) {", - " return \"\\\"\" + literalEscape(expectation.text) + \"\\\"\";", - " },", + "export interface IFilePosition {", + " offset: number", + " line: number", + " column: number", + "}", "", - " class(expectation) {", - " const escapedParts = expectation.parts.map((part) => {", - " return Array.isArray(part)", - " ? classEscape(part[0]) + \"-\" + classEscape(part[1])", - " : classEscape(part);", - " });", + "export interface IFileRange {", + " start: IFilePosition", + " end: IFilePosition", + "}", "", - " return \"[\" + (expectation.inverted ? \"^\" : \"\") + escapedParts + \"]\";", - " },", + "interface ILiteralExpectation {", + " type: \"literal\";", + " text: string;", + " ignoreCase: boolean;", + "}", "", - " any(): string {", - " return \"any character\";", - " },", + "interface IClassParts extends Array {}", "", - " end(): string {", - " return \"end of input\";", - " },", + "interface IClassExpectation {", + " type: \"class\";", + " parts: IClassParts;", + " inverted: boolean;", + " ignoreCase: boolean;", + "}", "", - " other(expectation): string {", - " return expectation.description;", - " }", - " };", + "interface IAnyExpectation {", + " type: \"any\";", + "}", "", + "interface IEndExpectation {", + " type: \"end\";", + "}", + "", + "interface IOtherExpectation {", + " type: \"other\";", + " description: string;", + "}", + "", + "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();", " }", @@ -824,11 +837,28 @@ function generateTS(ast, options) { " .replace(/[\\x10-\\x1F\\x7F-\\x9F]/g, (ch) => \"\\\\x\" + hex(ch) );", " }", "", - " function describeExpectation(expectation) {", - " return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);", + " 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) {", + " function describeExpected(expected1: Expectation[]) {", " const descriptions = expected1.map(describeExpectation);", " let i;", " let j;", @@ -859,7 +889,7 @@ function generateTS(ast, options) { " }", " }", "", - " function describeFound(found1) {", + " function describeFound(found1: string | null) {", " return found1 ? \"\\\"\" + literalEscape(found1) + \"\\\"\" : \"end of input\";", " }", "", @@ -867,12 +897,12 @@ function generateTS(ast, options) { " }", "", " public message: string;", - " public expected: string;", - " public found: string;", - " public location: any;", + " public expected: Expectation[];", + " public found: string | null;", + " public location: IFileRange;", " public name: string;", "", - " constructor(message: string, expected: string, found: string, location) {", + " constructor(message: string, expected: Expectation[], found: string | null, location: IFileRange) {", " super();", " this.message = message;", " this.expected = expected;", @@ -946,7 +976,7 @@ function generateTS(ast, options) { ].join("\n")); } parts.push([ - "function peg$parse(input, options) {", + "function peg$parse(input: string, options?: IParseOptions) {", " options = options !== undefined ? options : {};", "", " const peg$FAILED = {};", @@ -974,8 +1004,8 @@ function generateTS(ast, options) { let startRuleFunction = "peg$parse" + options.allowedStartRules[0]; parts.push([ - " const peg$startRuleFunctions = " + startRuleFunctions + ";", - " let peg$startRuleFunction = " + startRuleFunction + ";" + " const peg$startRuleFunctions: {[id: string]: any} = " + startRuleFunctions + ";", + " let peg$startRuleFunction: () => any = " + startRuleFunction + ";" ].join("\n")); } @@ -1028,7 +1058,7 @@ function generateTS(ast, options) { if (options.optimize === "size") { parts.push([ - " if (\"startRule\" in options) {", + " if (options.startRule !== undefined) {", " if (!(options.startRule in peg$startRuleIndices)) {", " throw new Error(\"Can't start parsing from rule \\\"\" + options.startRule + \"\\\".\");", " }", @@ -1038,7 +1068,7 @@ function generateTS(ast, options) { ].join("\n")); } else { parts.push([ - " if (\"startRule\" in options) {", + " if (options.startRule !== undefined) {", " if (!(options.startRule in peg$startRuleFunctions)) {", " throw new Error(\"Can't start parsing from rule \\\"\" + options.startRule + \"\\\".\");", " }", @@ -1054,11 +1084,11 @@ function generateTS(ast, options) { " return input.substring(peg$savedPos, peg$currPos);", " }", "", - " function location() {", + " function location(): IFileRange {", " return peg$computeLocation(peg$savedPos, peg$currPos);", " }", "", - " function expected(description, location1?) {", + " function expected(description: string, location1?: IFileRange) {", " location1 = location1 !== undefined", " ? location1", " : peg$computeLocation(peg$savedPos, peg$currPos);", @@ -1070,7 +1100,7 @@ function generateTS(ast, options) { " );", " }", "", - " function error(message: string, location1?) {", + " function error(message: string, location1?: IFileRange) {", " location1 = location1 !== undefined", " ? location1", " : peg$computeLocation(peg$savedPos, peg$currPos);", @@ -1078,23 +1108,23 @@ function generateTS(ast, options) { " throw peg$buildSimpleError(message, location1);", " }", "", - " function peg$literalExpectation(text1: string, ignoreCase: boolean) {", + " function peg$literalExpectation(text1: string, ignoreCase: boolean): ILiteralExpectation {", " return { type: \"literal\", text: text1, ignoreCase: ignoreCase };", " }", "", - " function peg$classExpectation(parts, inverted, ignoreCase: boolean) {", + " function peg$classExpectation(parts: IClassParts, inverted: boolean, ignoreCase: boolean): IClassExpectation {", " return { type: \"class\", parts: parts, inverted: inverted, ignoreCase: ignoreCase };", " }", "", - " function peg$anyExpectation() {", + " function peg$anyExpectation(): IAnyExpectation {", " return { type: \"any\" };", " }", "", - " function peg$endExpectation() {", + " function peg$endExpectation(): IEndExpectation {", " return { type: \"end\" };", " }", "", - " function peg$otherExpectation(description) {", + " function peg$otherExpectation(description: string): IOtherExpectation {", " return { type: \"other\", description: description };", " }", "", @@ -1133,7 +1163,7 @@ function generateTS(ast, options) { " }", " }", "", - " function peg$computeLocation(startPos: number, endPos: number) {", + " function peg$computeLocation(startPos: number, endPos: number): IFileRange {", " const startPosDetails = peg$computePosDetails(startPos);", " const endPosDetails = peg$computePosDetails(endPos);", "", @@ -1151,7 +1181,7 @@ function generateTS(ast, options) { " };", " }", "", - " function peg$fail(expected1) {", + " function peg$fail(expected1: Expectation) {", " if (peg$currPos < peg$maxFailPos) { return; }", "", " if (peg$currPos > peg$maxFailPos) {", @@ -1162,11 +1192,11 @@ function generateTS(ast, options) { " peg$maxFailExpected.push(expected1);", " }", "", - " function peg$buildSimpleError(message, location1) {", - " return new SyntaxError(message, \"\", \"\", location1);", + " function peg$buildSimpleError(message: string, location1: IFileRange) {", + " return new SyntaxError(message, [], \"\", location1);", " }", "", - " function peg$buildStructuredError(expected1, found, location1) {", + " function peg$buildStructuredError(expected1: Expectation[], found: string | null, location1: IFileRange) {", " return new SyntaxError(", " SyntaxError.buildMessage(expected1, found),", " expected1,", @@ -1394,7 +1424,7 @@ function generateTS(ast, options) { id => "\"" + js.stringEscape(id) + "\"" ).join(", ") + "]"; - let params = dependencyVars.join(", "); + let params = dependencyVars.map(v => v + ": any").join(", "); return [ generateGeneratedByComment(), @@ -1435,7 +1465,7 @@ function generateTS(ast, options) { let requires = dependencyIds.map( id => "require(\"" + js.stringEscape(id) + "\")" ).join(", "); - let params = dependencyVars.join(", "); + let params = dependencyVars.map(v => v + ": any").join(", "); parts.push([ generateGeneratedByComment(), From 2047ef686221dec688372066300a3378707f302d Mon Sep 17 00:00:00 2001 From: Joshua Worth Date: Sun, 14 Jan 2018 06:57:16 +0100 Subject: [PATCH 2/3] Added missing semicolons --- src/passes/generate-ts.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/passes/generate-ts.js b/src/passes/generate-ts.js index 3089c36..bef5d0b 100644 --- a/src/passes/generate-ts.js +++ b/src/passes/generate-ts.js @@ -765,14 +765,14 @@ function generateTS(ast, options) { } parts.push([ "export interface IFilePosition {", - " offset: number", - " line: number", - " column: number", + " offset: number;", + " line: number;", + " column: number;", "}", "", "export interface IFileRange {", - " start: IFilePosition", - " end: IFilePosition", + " start: IFilePosition;", + " end: IFilePosition;", "}", "", "interface ILiteralExpectation {", From ac79b114402422b820cd7eb81041b3f6858d6e79 Mon Sep 17 00:00:00 2001 From: Joshua Worth Date: Wed, 24 Jan 2018 00:08:48 +0100 Subject: [PATCH 3/3] Added more type information; Passing tests --- examples/arithmetics.pegjs | 4 +- src/passes/generate-ts.js | 112 ++++++++++++++++++++++--------------- tslint.json | 6 +- 3 files changed, 73 insertions(+), 49 deletions(-) diff --git a/examples/arithmetics.pegjs b/examples/arithmetics.pegjs index 7d4dea8..332aa5a 100644 --- a/examples/arithmetics.pegjs +++ b/examples/arithmetics.pegjs @@ -5,7 +5,7 @@ Expression = head:Term tail:(_ ("+" / "-") _ Term)* { - return tail.reduce(function(result, element) { + return tail.reduce(function(result: number, element: any[]) { if (element[1] === "+") { return result + element[3]; } if (element[1] === "-") { return result - element[3]; } }, head); @@ -13,7 +13,7 @@ Expression Term = head:Factor tail:(_ ("*" / "/") _ Factor)* { - return tail.reduce(function(result, element) { + return tail.reduce(function(result: number, element: any[]) { if (element[1] === "*") { return result * element[3]; } if (element[1] === "/") { return result / element[3]; } }, head); diff --git a/src/passes/generate-ts.js b/src/passes/generate-ts.js index bef5d0b..d61f19a 100644 --- a/src/passes/generate-ts.js +++ b/src/passes/generate-ts.js @@ -61,7 +61,7 @@ function generateTS(ast, options) { if (options.cache) { parts.push([ "const key = peg$currPos * " + ast.rules.length + " + " + ruleIndexCode + ";", - "const cached = peg$resultsCache[key];", + "const cached: ICached = peg$resultsCache[key];", "", "if (cached) {", " peg$currPos = cached.nextPos;", @@ -920,61 +920,83 @@ function generateTS(ast, options) { if (options.trace) { parts.push([ - "export function peg$DefaultTracer() {", - " this.indentLevel = 0;", + "interface ITraceEvent {", + " type: string;", + " rule: string;", + " result?: any;", + " location: IFileRange;", "}", "", - "peg$DefaultTracer.prototype.trace = function(event) {", - " const that = this;", + "export class DefaultTracer {", + " private indentLevel: number;", "", - " function log(evt) {", - " function repeat(text, n) {", - " let result = \"\", i;", + " constructor() {", + " this.indentLevel = 0;", + " }", "", - " for (i = 0; i < n; i++) {", - " result += text;", - " }", + " public trace(event: ITraceEvent) {", + " const that = this;", "", - " return result;", - " }", + " function log(evt: ITraceEvent) {", + " function repeat(text: string, n: number) {", + " let result = \"\", i;", "", - " function pad(text, length) {", - " return text + repeat(\" \", length - text.length);", - " }", + " for (i = 0; i < n; i++) {", + " result += text;", + " }", + "", + " return result;", + " }", "", - " if (typeof console === \"object\") {", // IE 8-10 - " 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", - " );", + " function pad(text: string, length: number) {", + " return text + repeat(\" \", length - text.length);", + " }", + "", + " if (typeof console === \"object\") {", // IE 8-10 + " 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;", + " switch (event.type) {", + " case \"rule.enter\":", + " log(event);", + " this.indentLevel++;", + " break;", "", - " case \"rule.match\":", - " this.indentLevel--;", - " log(event);", - " break;", + " case \"rule.match\":", + " this.indentLevel--;", + " log(event);", + " break;", "", - " case \"rule.fail\":", - " this.indentLevel--;", - " log(event);", - " break;", + " case \"rule.fail\":", + " this.indentLevel--;", + " log(event);", + " break;", "", - " default:", - " throw new Error(\"Invalid event type: \" + event.type + \".\");", + " default:", + " throw new Error(\"Invalid event type: \" + event.type + \".\");", + " }", " }", - "};", + "}", "" ].join("\n")); } + + if (options.cache) { + parts.push([ + "interface ICached {", + " nextPos: number;", + " result: any;", + "}", + "", + ].join("\n")); + } + parts.push([ "function peg$parse(input: string, options?: IParseOptions) {", " options = options !== undefined ? options : {};", @@ -1026,7 +1048,7 @@ function generateTS(ast, options) { if (options.cache) { parts.push([ - " const peg$resultsCache = {};", + " const peg$resultsCache: {[id: number]: ICached} = {};", "" ].join("\n")); } @@ -1046,7 +1068,7 @@ function generateTS(ast, options) { } parts.push([ - " const peg$tracer = \"tracer\" in options ? options.tracer : new peg$DefaultTracer();", + " const peg$tracer = \"tracer\" in options ? options.tracer : new DefaultTracer();", "" ].join("\n")); } @@ -1325,14 +1347,14 @@ function generateTS(ast, options) { return options.trace ? [ "{", - " peg$SyntaxError as SyntaxError,", - " peg$DefaultTracer as DefaultTracer,", + " SyntaxError as SyntaxError,", + " DefaultTracer as DefaultTracer,", " peg$parse as parse", "}" ].join("\n") : [ "{", - " peg$SyntaxError as SyntaxError,", + " SyntaxError as SyntaxError,", " peg$parse as parse", "}" ].join("\n"); diff --git a/tslint.json b/tslint.json index 32fa6e5..7676b75 100644 --- a/tslint.json +++ b/tslint.json @@ -4,6 +4,8 @@ "tslint:recommended" ], "jsRules": {}, - "rules": {}, + "rules": { + "max-classes-per-file": false + }, "rulesDirectory": [] -} \ No newline at end of file +}