651 lines
20 KiB
C#
651 lines
20 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.IO;
|
|
|
|
namespace Flee.Parsing
|
|
{
|
|
/**
|
|
* A recursive descent parser. This parser handles LL(n) grammars,
|
|
* selecting the appropriate pattern to parse based on the next few
|
|
* tokens. The parser is more efficient the fewer look-ahead tokens
|
|
* that is has to consider.
|
|
*/
|
|
internal class RecursiveDescentParser : Parser
|
|
{
|
|
private int _stackdepth = 0;
|
|
|
|
public RecursiveDescentParser(TextReader input) : base(input)
|
|
{
|
|
}
|
|
|
|
public RecursiveDescentParser(TextReader input, Analyzer analyzer)
|
|
: base(input, analyzer)
|
|
{
|
|
}
|
|
|
|
public RecursiveDescentParser(Tokenizer tokenizer)
|
|
: base(tokenizer)
|
|
{
|
|
}
|
|
|
|
public RecursiveDescentParser(Tokenizer tokenizer,
|
|
Analyzer analyzer)
|
|
: base(tokenizer, analyzer)
|
|
{
|
|
}
|
|
|
|
public override void AddPattern(ProductionPattern pattern)
|
|
{
|
|
|
|
// Check for empty matches
|
|
if (pattern.IsMatchingEmpty())
|
|
{
|
|
throw new ParserCreationException(
|
|
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
|
pattern.Name,
|
|
"zero elements can be matched (minimum is one)");
|
|
}
|
|
|
|
// Check for left-recusive patterns
|
|
if (pattern.IsLeftRecursive())
|
|
{
|
|
throw new ParserCreationException(
|
|
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
|
pattern.Name,
|
|
"left recursive patterns are not allowed");
|
|
}
|
|
|
|
// Add pattern
|
|
base.AddPattern(pattern);
|
|
}
|
|
|
|
public override void Prepare()
|
|
{
|
|
// Performs production pattern checks
|
|
base.Prepare();
|
|
SetInitialized(false);
|
|
|
|
// Calculate production look-ahead sets
|
|
var e = GetPatterns().GetEnumerator();
|
|
while (e.MoveNext())
|
|
{
|
|
CalculateLookAhead((ProductionPattern)e.Current);
|
|
}
|
|
|
|
// Set initialized flag
|
|
SetInitialized(true);
|
|
}
|
|
|
|
protected override Node ParseStart()
|
|
{
|
|
_stackdepth = 0;
|
|
var node = ParsePattern(GetStartPattern());
|
|
var token = PeekToken(0);
|
|
if (token != null)
|
|
{
|
|
var list = new ArrayList(1) { "<EOF>" };
|
|
throw new ParseException(
|
|
ParseException.ErrorType.UNEXPECTED_TOKEN,
|
|
token.ToShortString(),
|
|
list,
|
|
token.StartLine,
|
|
token.StartColumn);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
|
|
private Node ParsePattern(ProductionPattern pattern)
|
|
{
|
|
_stackdepth++;
|
|
|
|
if (_stackdepth > 200)
|
|
{
|
|
throw new System.StackOverflowException();
|
|
}
|
|
|
|
try
|
|
{
|
|
var defaultAlt = pattern.DefaultAlternative;
|
|
for (int i = 0; i < pattern.Count; i++)
|
|
{
|
|
var alt = pattern[i];
|
|
if (defaultAlt != alt && IsNext(alt))
|
|
{
|
|
return ParseAlternative(alt);
|
|
}
|
|
}
|
|
if (defaultAlt == null || !IsNext(defaultAlt))
|
|
{
|
|
ThrowParseException(FindUnion(pattern));
|
|
}
|
|
return ParseAlternative(defaultAlt);
|
|
}
|
|
finally
|
|
{
|
|
_stackdepth--;
|
|
}
|
|
}
|
|
|
|
private Node ParseAlternative(ProductionPatternAlternative alt)
|
|
{
|
|
var node = NewProduction(alt.Pattern);
|
|
EnterNode(node);
|
|
for (int i = 0; i < alt.Count; i++)
|
|
{
|
|
try
|
|
{
|
|
ParseElement(node, alt[i]);
|
|
}
|
|
catch (ParseException e)
|
|
{
|
|
AddError(e, true);
|
|
NextToken();
|
|
i--;
|
|
}
|
|
}
|
|
return ExitNode(node);
|
|
}
|
|
|
|
private void ParseElement(Production node,
|
|
ProductionPatternElement elem)
|
|
{
|
|
for (int i = 0; i < elem.MaxCount; i++)
|
|
{
|
|
if (i < elem.MinCount || IsNext(elem))
|
|
{
|
|
Node child;
|
|
if (elem.IsToken())
|
|
{
|
|
child = NextToken(elem.Id);
|
|
EnterNode(child);
|
|
AddNode(node, ExitNode(child));
|
|
}
|
|
else
|
|
{
|
|
child = ParsePattern(GetPattern(elem.Id));
|
|
AddNode(node, child);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsNext(ProductionPattern pattern)
|
|
{
|
|
LookAheadSet set = pattern.LookAhead;
|
|
|
|
if (set == null)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return set.IsNext(this);
|
|
}
|
|
}
|
|
|
|
private bool IsNext(ProductionPatternAlternative alt)
|
|
{
|
|
LookAheadSet set = alt.LookAhead;
|
|
|
|
if (set == null)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return set.IsNext(this);
|
|
}
|
|
}
|
|
|
|
private bool IsNext(ProductionPatternElement elem)
|
|
{
|
|
LookAheadSet set = elem.LookAhead;
|
|
|
|
if (set != null)
|
|
{
|
|
return set.IsNext(this);
|
|
}
|
|
else if (elem.IsToken())
|
|
{
|
|
return elem.IsMatch(PeekToken(0));
|
|
}
|
|
else
|
|
{
|
|
return IsNext(GetPattern(elem.Id));
|
|
}
|
|
}
|
|
|
|
private void CalculateLookAhead(ProductionPattern pattern)
|
|
{
|
|
ProductionPatternAlternative alt;
|
|
LookAheadSet previous = new LookAheadSet(0);
|
|
int length = 1;
|
|
int i;
|
|
CallStack stack = new CallStack();
|
|
|
|
// Calculate simple look-ahead
|
|
stack.Push(pattern.Name, 1);
|
|
var result = new LookAheadSet(1);
|
|
var alternatives = new LookAheadSet[pattern.Count];
|
|
for (i = 0; i < pattern.Count; i++)
|
|
{
|
|
alt = pattern[i];
|
|
alternatives[i] = FindLookAhead(alt, 1, 0, stack, null);
|
|
alt.LookAhead = alternatives[i];
|
|
result.AddAll(alternatives[i]);
|
|
}
|
|
if (pattern.LookAhead == null)
|
|
{
|
|
pattern.LookAhead = result;
|
|
}
|
|
var conflicts = FindConflicts(pattern, 1);
|
|
|
|
// Resolve conflicts
|
|
while (conflicts.Size() > 0)
|
|
{
|
|
length++;
|
|
stack.Clear();
|
|
stack.Push(pattern.Name, length);
|
|
conflicts.AddAll(previous);
|
|
for (i = 0; i < pattern.Count; i++)
|
|
{
|
|
alt = pattern[i];
|
|
if (alternatives[i].Intersects(conflicts))
|
|
{
|
|
alternatives[i] = FindLookAhead(alt,
|
|
length,
|
|
0,
|
|
stack,
|
|
conflicts);
|
|
alt.LookAhead = alternatives[i];
|
|
}
|
|
if (alternatives[i].Intersects(conflicts))
|
|
{
|
|
if (pattern.DefaultAlternative == null)
|
|
{
|
|
pattern.DefaultAlternative = alt;
|
|
}
|
|
else if (pattern.DefaultAlternative != alt)
|
|
{
|
|
result = alternatives[i].CreateIntersection(conflicts);
|
|
ThrowAmbiguityException(pattern.Name,
|
|
null,
|
|
result);
|
|
}
|
|
}
|
|
}
|
|
previous = conflicts;
|
|
conflicts = FindConflicts(pattern, length);
|
|
}
|
|
|
|
// Resolve conflicts inside rules
|
|
for (i = 0; i < pattern.Count; i++)
|
|
{
|
|
CalculateLookAhead(pattern[i], 0);
|
|
}
|
|
}
|
|
|
|
private void CalculateLookAhead(ProductionPatternAlternative alt,
|
|
int pos)
|
|
{
|
|
LookAheadSet previous = new LookAheadSet(0);
|
|
int length = 1;
|
|
|
|
// Check trivial cases
|
|
if (pos >= alt.Count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check for non-optional element
|
|
var pattern = alt.Pattern;
|
|
var elem = alt[pos];
|
|
if (elem.MinCount == elem.MaxCount)
|
|
{
|
|
CalculateLookAhead(alt, pos + 1);
|
|
return;
|
|
}
|
|
|
|
// Calculate simple look-aheads
|
|
var first = FindLookAhead(elem, 1, new CallStack(), null);
|
|
var follow = FindLookAhead(alt, 1, pos + 1, new CallStack(), null);
|
|
|
|
// Resolve conflicts
|
|
var location = "at position " + (pos + 1);
|
|
var conflicts = FindConflicts(pattern.Name,
|
|
location,
|
|
first,
|
|
follow);
|
|
while (conflicts.Size() > 0)
|
|
{
|
|
length++;
|
|
conflicts.AddAll(previous);
|
|
first = FindLookAhead(elem,
|
|
length,
|
|
new CallStack(),
|
|
conflicts);
|
|
follow = FindLookAhead(alt,
|
|
length,
|
|
pos + 1,
|
|
new CallStack(),
|
|
conflicts);
|
|
first = first.CreateCombination(follow);
|
|
elem.LookAhead = first;
|
|
if (first.Intersects(conflicts))
|
|
{
|
|
first = first.CreateIntersection(conflicts);
|
|
ThrowAmbiguityException(pattern.Name, location, first);
|
|
}
|
|
previous = conflicts;
|
|
conflicts = FindConflicts(pattern.Name,
|
|
location,
|
|
first,
|
|
follow);
|
|
}
|
|
|
|
// Check remaining elements
|
|
CalculateLookAhead(alt, pos + 1);
|
|
}
|
|
|
|
private LookAheadSet FindLookAhead(ProductionPattern pattern,
|
|
int length,
|
|
CallStack stack,
|
|
LookAheadSet filter)
|
|
{
|
|
// Check for infinite loop
|
|
if (stack.Contains(pattern.Name, length))
|
|
{
|
|
throw new ParserCreationException(
|
|
ParserCreationException.ErrorType.INFINITE_LOOP,
|
|
pattern.Name,
|
|
(String)null);
|
|
}
|
|
|
|
// Find pattern look-ahead
|
|
stack.Push(pattern.Name, length);
|
|
var result = new LookAheadSet(length);
|
|
for (int i = 0; i < pattern.Count; i++)
|
|
{
|
|
var temp = FindLookAhead(pattern[i],
|
|
length,
|
|
0,
|
|
stack,
|
|
filter);
|
|
result.AddAll(temp);
|
|
}
|
|
stack.Pop();
|
|
|
|
return result;
|
|
}
|
|
|
|
private LookAheadSet FindLookAhead(ProductionPatternAlternative alt,
|
|
int length,
|
|
int pos,
|
|
CallStack stack,
|
|
LookAheadSet filter)
|
|
{
|
|
LookAheadSet follow;
|
|
// Check trivial cases
|
|
if (length <= 0 || pos >= alt.Count)
|
|
{
|
|
return new LookAheadSet(0);
|
|
}
|
|
|
|
// Find look-ahead for this element
|
|
var first = FindLookAhead(alt[pos], length, stack, filter);
|
|
if (alt[pos].MinCount == 0)
|
|
{
|
|
first.AddEmpty();
|
|
}
|
|
|
|
// Find remaining look-ahead
|
|
if (filter == null)
|
|
{
|
|
length -= first.GetMinLength();
|
|
if (length > 0)
|
|
{
|
|
follow = FindLookAhead(alt, length, pos + 1, stack, null);
|
|
first = first.CreateCombination(follow);
|
|
}
|
|
}
|
|
else if (filter.IsOverlap(first))
|
|
{
|
|
var overlaps = first.CreateOverlaps(filter);
|
|
length -= overlaps.GetMinLength();
|
|
filter = filter.CreateFilter(overlaps);
|
|
follow = FindLookAhead(alt, length, pos + 1, stack, filter);
|
|
first.RemoveAll(overlaps);
|
|
first.AddAll(overlaps.CreateCombination(follow));
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
private LookAheadSet FindLookAhead(ProductionPatternElement elem,
|
|
int length,
|
|
CallStack stack,
|
|
LookAheadSet filter)
|
|
{
|
|
// Find initial element look-ahead
|
|
var first = FindLookAhead(elem, length, 0, stack, filter);
|
|
var result = new LookAheadSet(length);
|
|
result.AddAll(first);
|
|
if (filter == null || !filter.IsOverlap(result))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// Handle element repetitions
|
|
if (elem.MaxCount == Int32.MaxValue)
|
|
{
|
|
first = first.CreateRepetitive();
|
|
}
|
|
var max = elem.MaxCount;
|
|
if (length < max)
|
|
{
|
|
max = length;
|
|
}
|
|
for (int i = 1; i < max; i++)
|
|
{
|
|
first = first.CreateOverlaps(filter);
|
|
if (first.Size() <= 0 || first.GetMinLength() >= length)
|
|
{
|
|
break;
|
|
}
|
|
var follow = FindLookAhead(elem,
|
|
length,
|
|
0,
|
|
stack,
|
|
filter.CreateFilter(first));
|
|
first = first.CreateCombination(follow);
|
|
result.AddAll(first);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private LookAheadSet FindLookAhead(ProductionPatternElement elem,
|
|
int length,
|
|
int dummy,
|
|
CallStack stack,
|
|
LookAheadSet filter)
|
|
{
|
|
LookAheadSet result;
|
|
|
|
if (elem.IsToken())
|
|
{
|
|
result = new LookAheadSet(length);
|
|
result.Add(elem.Id);
|
|
}
|
|
else
|
|
{
|
|
var pattern = GetPattern(elem.Id);
|
|
result = FindLookAhead(pattern, length, stack, filter);
|
|
if (stack.Contains(pattern.Name))
|
|
{
|
|
result = result.CreateRepetitive();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private LookAheadSet FindConflicts(ProductionPattern pattern,
|
|
int maxLength)
|
|
{
|
|
|
|
LookAheadSet result = new LookAheadSet(maxLength);
|
|
for (int i = 0; i < pattern.Count; i++)
|
|
{
|
|
var set1 = pattern[i].LookAhead;
|
|
for (int j = 0; j < i; j++)
|
|
{
|
|
var set2 = pattern[j].LookAhead;
|
|
result.AddAll(set1.CreateIntersection(set2));
|
|
}
|
|
}
|
|
if (result.IsRepetitive())
|
|
{
|
|
ThrowAmbiguityException(pattern.Name, null, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private LookAheadSet FindConflicts(string pattern,
|
|
string location,
|
|
LookAheadSet set1,
|
|
LookAheadSet set2)
|
|
{
|
|
var result = set1.CreateIntersection(set2);
|
|
if (result.IsRepetitive())
|
|
{
|
|
ThrowAmbiguityException(pattern, location, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private LookAheadSet FindUnion(ProductionPattern pattern)
|
|
{
|
|
LookAheadSet result;
|
|
int length = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < pattern.Count; i++)
|
|
{
|
|
result = pattern[i].LookAhead;
|
|
if (result.GetMaxLength() > length)
|
|
{
|
|
length = result.GetMaxLength();
|
|
}
|
|
}
|
|
result = new LookAheadSet(length);
|
|
for (i = 0; i < pattern.Count; i++)
|
|
{
|
|
result.AddAll(pattern[i].LookAhead);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
private void ThrowParseException(LookAheadSet set)
|
|
{
|
|
ArrayList list = new ArrayList();
|
|
|
|
// Read tokens until mismatch
|
|
while (set.IsNext(this, 1))
|
|
{
|
|
set = set.CreateNextSet(NextToken().Id);
|
|
}
|
|
|
|
// Find next token descriptions
|
|
var initials = set.GetInitialTokens();
|
|
for (int i = 0; i < initials.Length; i++)
|
|
{
|
|
list.Add(GetTokenDescription(initials[i]));
|
|
}
|
|
|
|
// Create exception
|
|
var token = NextToken();
|
|
throw new ParseException(ParseException.ErrorType.UNEXPECTED_TOKEN,
|
|
token.ToShortString(),
|
|
list,
|
|
token.StartLine,
|
|
token.StartColumn);
|
|
}
|
|
|
|
private void ThrowAmbiguityException(string pattern,
|
|
string location,
|
|
LookAheadSet set)
|
|
{
|
|
|
|
ArrayList list = new ArrayList();
|
|
|
|
// Find next token descriptions
|
|
var initials = set.GetInitialTokens();
|
|
for (int i = 0; i < initials.Length; i++)
|
|
{
|
|
list.Add(GetTokenDescription(initials[i]));
|
|
}
|
|
|
|
// Create exception
|
|
throw new ParserCreationException(
|
|
ParserCreationException.ErrorType.INHERENT_AMBIGUITY,
|
|
pattern,
|
|
location,
|
|
list);
|
|
}
|
|
|
|
|
|
private class CallStack
|
|
{
|
|
private readonly ArrayList _nameStack = new ArrayList();
|
|
private readonly ArrayList _valueStack = new ArrayList();
|
|
public bool Contains(string name)
|
|
{
|
|
return _nameStack.Contains(name);
|
|
}
|
|
|
|
public bool Contains(string name, int value)
|
|
{
|
|
for (int i = 0; i < _nameStack.Count; i++)
|
|
{
|
|
if (_nameStack[i].Equals(name)
|
|
&& _valueStack[i].Equals(value))
|
|
{
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_nameStack.Clear();
|
|
_valueStack.Clear();
|
|
}
|
|
|
|
public void Push(string name, int value)
|
|
{
|
|
_nameStack.Add(name);
|
|
_valueStack.Add(value);
|
|
}
|
|
|
|
public void Pop()
|
|
{
|
|
if (_nameStack.Count > 0)
|
|
{
|
|
_nameStack.RemoveAt(_nameStack.Count - 1);
|
|
_valueStack.RemoveAt(_valueStack.Count - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|