You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

302 lines
11 KiB

/******************************************************************************
* Copyright 2022 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { createToken, EmbeddedActionsParser, EOF, IToken, TokenType } from "chevrotain"
import { LLStarLookaheadStrategy } from "./all-star-lookahead"
describe("ATN Simulator", () => {
describe("LL(*) lookahead", () => {
const A = createToken({ name: "A", pattern: "a" })
const B = createToken({ name: "B", pattern: "b" })
class UnboundedLookaheadParser extends EmbeddedActionsParser {
constructor() {
super([A, B], {
lookaheadStrategy: new LLStarLookaheadStrategy()
})
this.performSelfAnalysis()
}
LongRule = this.RULE("LongRule", () => {
return this.OR([
{
ALT: () => {
return 0
}
},
{
ALT: () => {
this.AT_LEAST_ONE1(() => this.CONSUME1(A))
return 1
}
},
{
ALT: () => {
this.AT_LEAST_ONE2(() => this.CONSUME2(A))
this.CONSUME(B)
return 2
}
}
])
})
}
it("Should pick longest alternative instead of first #1", () => {
const parser = new UnboundedLookaheadParser()
parser.input = [
createRegularToken(A),
createRegularToken(A),
createRegularToken(A)
]
const result = parser.LongRule()
expect(result).toBe(1);
})
it("Should pick longest alternative instead of first #2", () => {
const parser = new UnboundedLookaheadParser()
parser.input = [
createRegularToken(A),
createRegularToken(A),
createRegularToken(B)
]
const result = parser.LongRule()
expect(result).toBe(2);
})
it("Should pick shortest fitting alternative", () => {
const parser = new UnboundedLookaheadParser()
parser.input = []
const result = parser.LongRule()
expect(result).toBe(0);
})
})
describe("Ambiguity Detection", () => {
const A = createToken({ name: "A" })
const B = createToken({ name: "B" })
class AmbigiousParser extends EmbeddedActionsParser {
ambiguityReports: string[] = []
constructor() {
super([A, B], {
lookaheadStrategy: new LLStarLookaheadStrategy({
logging: (message) => this.ambiguityReports.push(message)
})
});
this.performSelfAnalysis()
}
OptionRule = this.RULE("OptionRule", () => {
let usedOption = false
this.OPTION(() => {
this.AT_LEAST_ONE1(() => this.CONSUME1(A))
usedOption = true
})
this.AT_LEAST_ONE2(() => this.CONSUME2(A))
return usedOption
})
AltRule = this.RULE("AltRule", () => {
return this.OR([
{
ALT: () => {
this.SUBRULE(this.RuleB)
return 0
}
},
{
ALT: () => {
this.SUBRULE(this.RuleC)
return 1
}
}
])
})
RuleB = this.RULE("RuleB", () => {
this.MANY(() => this.CONSUME(A))
})
RuleC = this.RULE("RuleC", () => {
this.MANY(() => this.CONSUME(A))
this.OPTION(() => this.CONSUME(B))
})
AltRuleWithEOF = this.RULE("AltRuleWithEOF", () => {
return this.OR([
{
ALT: () => {
this.SUBRULE1(this.RuleEOF)
return 0
}
},
{
ALT: () => {
this.SUBRULE2(this.RuleEOF)
return 1
}
}
])
})
RuleEOF = this.RULE("RuleEOF", () => {
this.MANY1(() => this.CONSUME(A))
this.CONSUME(EOF)
})
AltRuleWithPred = this.RULE("AltRuleWithPred", (pred?: boolean) => {
return this.OR([
{
ALT: () => {
this.CONSUME1(A)
return 0
},
GATE: () => (pred === undefined ? true : pred)
},
{
ALT: () => {
this.CONSUME2(A)
return 1
},
GATE: () => (pred === undefined ? true : !pred)
},
{
ALT: () => {
this.CONSUME(B)
return 2
}
}
])
})
AltWithOption = this.RULE("AltWithOption", () => {
const intermediate = this.OR([
{
ALT: () => {
this.CONSUME1(A)
return 2
}
},
{
ALT: () => {
this.CONSUME2(B)
return 4
}
}
])
const option = this.OPTION(() => {
this.CONSUME3(A);
return 1;
})
return (option ?? 0) + intermediate;
})
}
it("Should pick option on ambiguity", () => {
const parser = new AmbigiousParser()
parser.input = [
createRegularToken(A),
createRegularToken(A),
createRegularToken(A)
]
const result = parser.OptionRule()
expect(result).toBeTruthy();
// The rule nests a `AT_LEAST_ONE` inside and outside the OPTION
// Both productions produce lookahead ambiguities
expect(parser.ambiguityReports[0]).toMatch("<0, 1> in <OPTION>")
expect(parser.ambiguityReports[1]).toMatch("<0, 1> in <AT_LEAST_ONE1>")
})
it("Should pick first alternative on ambiguity", () => {
const parser = new AmbigiousParser()
parser.input = [
createRegularToken(A),
createRegularToken(A),
createRegularToken(A)
]
const result = parser.AltRule()
expect(result).toBe(0);
expect(parser.ambiguityReports[0]).toMatch("<0, 1> in <OR>")
})
it("Should pick first alternative on EOF ambiguity", () => {
const parser = new AmbigiousParser()
parser.input = []
const result = parser.AltRuleWithEOF()
expect(result).toBe(0);
expect(parser.ambiguityReports[0]).toMatch("<0, 1> in <OR>")
})
it("Should pick correct alternative on long prefix", () => {
const parser = new AmbigiousParser()
parser.input = [
createRegularToken(A),
createRegularToken(A),
createRegularToken(B)
]
const result = parser.AltRule()
expect(result).toBe(1);
expect(parser.ambiguityReports).toHaveLength(0);
})
it("Should resolve ambiguity using predicate", () => {
const parser = new AmbigiousParser()
parser.input = [createRegularToken(A)]
const resultAutomatic = parser.AltRuleWithPred(undefined)
// Automatically resolving the ambiguity should return `0`
expect(resultAutomatic).toBe(0);
// It should also create an ambiguity report
expect(parser.ambiguityReports[0]).toMatch("<0, 1> in <OR>")
parser.ambiguityReports = []
parser.input = [createRegularToken(A)]
const resultTrue = parser.AltRuleWithPred(true)
expect(resultTrue).toBe(0);
parser.input = [createRegularToken(A)]
const resultFalse = parser.AltRuleWithPred(false)
expect(resultFalse).toBe(1),
expect(parser.ambiguityReports).toHaveLength(0);
})
it("Should pick non-ambigious alternative inside of ambigious, predicated alternation", () => {
const parser = new AmbigiousParser()
parser.input = [createRegularToken(B)]
const result = parser.AltRuleWithPred(undefined)
expect(result).toBe(2);
expect(parser.ambiguityReports).toHaveLength(0);
})
it("Should work with alternatives followed by optional elements", () => {
const parser = new AmbigiousParser();
parser.input = [createRegularToken(B), createRegularToken(A)];
const result = parser.AltWithOption();
expect(result).toBe(5);
})
})
})
function createRegularToken(
tokType: TokenType,
image = "",
startOffset = 1,
startLine?: number,
startColumn?: number,
endOffset?: number,
endLine?: number,
endColumn?: number
): IToken {
return {
image: image,
startOffset: startOffset,
startLine: startLine,
startColumn: startColumn,
endOffset: endOffset,
endLine: endLine,
endColumn: endColumn,
tokenTypeIdx: tokType.tokenTypeIdx!,
tokenType: tokType
}
}