-
Notifications
You must be signed in to change notification settings - Fork 202
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* The validation is only performed once when the first Parser instance is created. * If a problem is detected an Error will be thrown (fail fast) * Certain combinations of DSL productions and their arguments cannot appear more than once inside the same top level rule. The disallowed combinations: * CONSUME : the same occurrence index and the same terminal identity * SUBRULE: the same occurrence index and the same subrule invoked * MANY, OR, OPTION, AT_LEAST_ONE: the same occurrence index * These rules are necessary as the above combinations indicate the current position in the grammar during runtime. This information is needed in order to perform error recovery/ build automatic lookahead functions / ...
- Loading branch information
1 parent
4bd0b2f
commit 589f9b2
Showing
9 changed files
with
316 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/// <reference path="gast.ts" /> | ||
/// <reference path="../../../libs/lodash.d.ts" /> | ||
|
||
module chevrotain.validations { | ||
|
||
import gast = chevrotain.gast | ||
|
||
export class GrammarError implements Error { | ||
|
||
public get message() :string { | ||
return getProductionDslName(this.refs[0]) + " with occurence number " + (<any>this.refs[0]).occurrenceInParent + | ||
" appears " + this.refs.length + " times in " + this.topLevelRule.name | ||
+ " with the same occurrence number" | ||
} | ||
|
||
//In order to show the message in the printed error in the console | ||
public toString() :string { | ||
return this.name + ": " + this.message | ||
} | ||
|
||
constructor(public refs: gast.IProduction[], | ||
public topLevelRule: gast.TOP_LEVEL, | ||
public name: string = "Occurrence Number Error" ) { | ||
} | ||
} | ||
|
||
export function validateGrammar(topLevels:gast.TOP_LEVEL[]): GrammarError[] { | ||
var errorsArrays = _.map(topLevels, (topLevel) => { | ||
var collectorVisitor = new OccurrenceValidationCollector() | ||
topLevel.accept(collectorVisitor) | ||
return validateOccurrenceUsageInProductions(collectorVisitor.allProductions, topLevel) | ||
}) | ||
|
||
return _.flatten<GrammarError>(errorsArrays) | ||
} | ||
|
||
export function getProductionDslName(prod: gast.IProduction) { | ||
if (prod instanceof gast.Terminal) { | ||
return "CONSUME" | ||
} else if ( prod instanceof gast.ProdRef) { | ||
return "SUBRULE" | ||
} else { | ||
return lang.functionName((<any>prod).constructor) | ||
} | ||
} | ||
|
||
export function getRelevantProductionArgument(prod: gast.IProduction, defaultProductionArgument: string = "...") { | ||
if (prod instanceof gast.Terminal) { | ||
return lang.functionName((<gast.Terminal>prod).terminalType) | ||
} else if ( prod instanceof gast.ProdRef) { | ||
return (<gast.ProdRef>prod).refProdName | ||
} else { | ||
return defaultProductionArgument | ||
} | ||
} | ||
|
||
export function identifyProductionForDuplicates(prod: gast.IProduction) { | ||
return getProductionDslName(prod) + (<any>prod).occurrenceInParent + "(" + getRelevantProductionArgument(prod) + ")" | ||
} | ||
|
||
export function productionOccurrenceErrorGenerator(prods:gast.IProduction[], topLevelRule:gast.TOP_LEVEL) : GrammarError { | ||
//All the productions should be of the same type and they have the same occurrence number | ||
var representativeProduction = prods[0] | ||
return new GrammarError(prods, topLevelRule) | ||
} | ||
|
||
export function validateOccurrenceUsageInProductions(productions:gast.IProduction[], topLevel:gast.TOP_LEVEL) : GrammarError[] { | ||
var groups = _.groupBy(productions, identifyProductionForDuplicates) | ||
|
||
//Cannot use _.filter because groups is an object of arrays and not an array | ||
var errors = [] | ||
_.forEach(groups, function(groupValues, groupKey) { | ||
if (groupValues.length > 1) { | ||
errors.push(productionOccurrenceErrorGenerator(groupValues, topLevel)) | ||
} | ||
}) | ||
|
||
return errors | ||
} | ||
|
||
export class OccurrenceValidationCollector extends gast.GAstVisitor { | ||
public allProductions : gast.IProduction[]= [] | ||
|
||
public visitProdRef(subrule:gast.ProdRef):void { | ||
this.allProductions.push(subrule) | ||
} | ||
|
||
public visitOPTION(option:gast.OPTION):void { | ||
this.allProductions.push(option) | ||
} | ||
|
||
public visitAT_LEAST_ONE(atLeastOne:gast.AT_LEAST_ONE):void { | ||
this.allProductions.push(atLeastOne) | ||
} | ||
|
||
public visitMANY(many:gast.MANY):void { | ||
this.allProductions.push(many) | ||
} | ||
|
||
public visitOR(or:gast.OR):void { | ||
this.allProductions.push(or) | ||
} | ||
|
||
public visitTerminal(terminal:gast.Terminal):void { | ||
this.allProductions.push(terminal) | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
module chevrotain.validations.spec { | ||
import gast = chevrotain.gast | ||
import tok = chevrotain.tokens | ||
import samples = test.samples | ||
|
||
describe("Grammar Validations module", function () { | ||
|
||
describe("validateGrammar", function () { | ||
|
||
it("validates every one of the TOP_RULEs in the input", function () { | ||
var qualifiedNameErr1 = new gast.TOP_LEVEL("qualifiedNameErr1", [ | ||
new gast.Terminal(samples.IdentTok, 1), | ||
new gast.MANY([ | ||
new gast.Terminal(samples.DotTok), | ||
new gast.Terminal(samples.IdentTok, 1)//this is the error | ||
]) | ||
]) | ||
|
||
var qualifiedNameErr2 = new gast.TOP_LEVEL("qualifiedNameErr2", [ | ||
new gast.Terminal(samples.IdentTok, 1), | ||
new gast.MANY([ | ||
new gast.Terminal(samples.DotTok), | ||
new gast.Terminal(samples.IdentTok, 2) | ||
]), | ||
new gast.MANY([ | ||
new gast.Terminal(samples.DotTok), | ||
new gast.Terminal(samples.IdentTok, 2) | ||
]) | ||
]) | ||
var errors = validateGrammar([qualifiedNameErr1, qualifiedNameErr2]) | ||
expect(errors.length).toBe(4) | ||
}) | ||
}) | ||
|
||
describe("identifyProductionForDuplicates function", function () { | ||
it("generates DSL code for a ProdRef", function () { | ||
var dslCode = identifyProductionForDuplicates(new gast.ProdRef("this.ActionDeclaration")) | ||
expect(dslCode).toBe("SUBRULE1(this.ActionDeclaration)") | ||
}) | ||
|
||
it("generates DSL code for a OPTION", function () { | ||
var dslCode = identifyProductionForDuplicates(new gast.OPTION([], 1)) | ||
expect(dslCode).toBe("OPTION1(...)") | ||
}) | ||
|
||
it("generates DSL code for a AT_LEAST_ONE", function () { | ||
var dslCode = identifyProductionForDuplicates(new gast.AT_LEAST_ONE([], 1)) | ||
expect(dslCode).toBe("AT_LEAST_ONE1(...)") | ||
}) | ||
|
||
it("generates DSL code for a MANY", function () { | ||
var dslCode = identifyProductionForDuplicates(new gast.MANY([], 1)) | ||
expect(dslCode).toBe("MANY1(...)") | ||
}) | ||
|
||
it("generates DSL code for a OR", function () { | ||
var dslCode = identifyProductionForDuplicates(new gast.OR([], 1)) | ||
expect(dslCode).toBe("OR1(...)") | ||
}) | ||
|
||
it("generates DSL code for a Terminal", function () { | ||
var dslCode = identifyProductionForDuplicates(new gast.Terminal(samples.IdentTok, 1)) | ||
expect(dslCode).toBe("CONSUME1(IdentTok)") | ||
}) | ||
}) | ||
|
||
describe("The error generator function", function () { | ||
it("generates the correct error for OPTION with the same occurrence number", function () { | ||
var options : [gast.OPTION] = [ | ||
new gast.OPTION([], 1), | ||
new gast.OPTION([], 1), | ||
new gast.OPTION([], 1) | ||
] | ||
var error = productionOccurrenceErrorGenerator(options, null) | ||
expect(error).toBeDefined() | ||
expect(getProductionDslName(error.refs[0])).toBe("OPTION") | ||
expect(error.refs.length).toBe(3) | ||
}) | ||
|
||
it("generates the correct error for AT_LEAST_ONE with the same occurrence number", function () { | ||
var atLeastOne : [gast.AT_LEAST_ONE] = [ | ||
new gast.AT_LEAST_ONE([], 2), | ||
new gast.AT_LEAST_ONE([], 2) | ||
] | ||
var error = productionOccurrenceErrorGenerator(atLeastOne, null) | ||
expect(error).toBeDefined() | ||
expect(getProductionDslName(error.refs[0])).toBe("AT_LEAST_ONE") | ||
expect(error.refs.length).toBe(2) | ||
}) | ||
|
||
it("generates the correct error for terminals with the same token and occurrence number", function () { | ||
var consumeToks : [gast.Terminal] = [ | ||
new gast.Terminal(samples.IdentTok, 1), | ||
new gast.Terminal(samples.IdentTok, 1), | ||
new gast.Terminal(samples.IdentTok, 1) | ||
] | ||
var error = productionOccurrenceErrorGenerator(consumeToks, null) | ||
expect(error).toBeDefined() | ||
expect(getProductionDslName(error.refs[0])).toBe("CONSUME") | ||
expect(error.refs.length).toBe(3) | ||
}) | ||
|
||
it("generates the correct error for ProdRef with the same referenced rule and occurrence number", function () { | ||
var subRules : [gast.ProdRef] = [ | ||
new gast.ProdRef("this.GroupBy"), | ||
new gast.ProdRef("this.GroupBy"), | ||
new gast.ProdRef("this.GroupBy") | ||
] | ||
var error = productionOccurrenceErrorGenerator(subRules, null) | ||
expect(error).toBeDefined() | ||
expect(getProductionDslName(error.refs[0])).toBe("SUBRULE") | ||
expect(error.refs.length).toBe(3) | ||
}) | ||
|
||
}) | ||
|
||
describe("OccurrenceValidationCollector", function () { | ||
|
||
it("collects all the productions relevant to occurrence validation", function () { | ||
var qualifiedNameVisitor = new OccurrenceValidationCollector() | ||
samples.qualifiedName.accept(qualifiedNameVisitor) | ||
expect(qualifiedNameVisitor.allProductions.length).toBe(4) | ||
|
||
var actionDecVisitor = new OccurrenceValidationCollector() | ||
samples.actionDec.accept(actionDecVisitor) | ||
expect(actionDecVisitor.allProductions.length).toBe(13) | ||
}) | ||
|
||
}) | ||
|
||
|
||
describe("The GrammarError class", function () { | ||
|
||
it("generates the correct error message", function () { | ||
var grammarError = new GrammarError( | ||
[new gast.Terminal(samples.ActionTok), | ||
new gast.Terminal(samples.ActionTok)], | ||
new gast.TOP_LEVEL("batata", [])) | ||
|
||
expect(grammarError.toString()).toBe("Occurrence Number Error: CONSUME with occurence number 1 " + | ||
"appears 2 times in batata with the same occurrence number") | ||
expect(grammarError.message).toBe("CONSUME with occurence number 1 appears 2 times in batata with " + | ||
"the same occurrence number") | ||
}) | ||
|
||
}) | ||
}) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.