private Rule Compile(UncompiledRule uncompiledRule, ref int nextListRule, List <Rule> listRules) { var option = uncompiledRule.Choices; var m = uncompiledRule.Method; var steps = new List <Step>(); var args = new List <bool>(); foreach (var p in m.GetParameters()) { var tag = p.GetCustomAttribute <ContextAttribute>()?.GetTag(p.ParameterType); var nonTerminal = p.GetCustomAttribute <NonTerminalAttribute>(); if (nonTerminal != null) { if (nonTerminal.Optional) { var keep = option % 2 == 1; option = option / 2; if (!keep) { args.Add(false); continue; } } // Non-terminal step var type = p.ParameterType; if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Pos <>) || type.GetGenericTypeDefinition() == typeof(Nullable <>))) { type = type.GetGenericArguments()[0]; } if (type.IsArray) { throw new Exception($"Parameter {p.Name} in {m.Name}, array type {type} only allowed for lists."); } if (!MaxRank.TryGetValue(type, out var typeMaxRank)) { throw new Exception($"Parameter {p.Name} in {m.Name}, unknown type {type}."); } if (nonTerminal.MaxRank != null) { typeMaxRank = (int)nonTerminal.MaxRank; } var source = Enumerable.Range(0, typeMaxRank + 1) .SelectMany(i => RankedTypeId.TryGetValue(new RankedType(type, i), out var id) ? RuleId[id] : new int[0]) .ToArray(); steps.Add(new Step(source, tag, false)); args.Add(true); continue; } var terminal = p.GetCustomAttribute <TerminalAttribute>(); if (terminal != null) { if (terminal.Optional) { var keep = option % 2 == 1; option = option / 2; args.Add(keep); if (!keep) { continue; } } else { args.Add(true); } var type = p.ParameterType; if (type != TokenType && type != typeof(Nullable <>).MakeGenericType(TokenType) && type != typeof(string) && type != typeof(Pos <string>)) { throw new Exception($"Parameter {p.Name} in {m.Name}, unknown terminal type {type}."); } // Terminal step steps.Add(new Step(Expand(terminal.Tokens, PublicChildren), tag, true)); continue; } var list = p.GetCustomAttribute <ListAttribute>(); if (list != null) { if (list.Min == 0) { // The zero-element list is handled in a separate rule, so the code // below can assume that it has at least one element. var keep = option % 2 == 1; option = option / 2; args.Add(keep); if (!keep) { continue; } } else { args.Add(true); } // List step var type = p.ParameterType; if (!type.IsArray || type.GetArrayRank() != 1) { throw new Exception($"Parameter {p.Name} in {m.Name}, unsupported list type {type}."); } type = type.GetElementType(); var separator = list.Separator != null ? Expand(new[] { list.Separator.Value }, PublicChildren) : list.Terminator != null ? Expand(new[] { list.Terminator.Value }, PublicChildren) : null; if (!MaxRank.TryGetValue(type, out var typeMaxRank)) { throw new Exception($"Parameter {p.Name} in {m.Name}, unknown type {type}."); } if (list.MaxRank != null) { typeMaxRank = (int)list.MaxRank; } var itemSource = Enumerable.Range(0, typeMaxRank + 1) .SelectMany(i => RankedTypeId.TryGetValue(new RankedType(type, i), out var id) ? RuleId[id] : new int[0]) .ToArray(); // The list is split as (LOOP* END) with: // // LIST = LOOP LIST // | END // // So there are always two rules to consider. var loopKey = new TypeLoopKey(list, type); if (!_typeLoops.TryGetValue(loopKey, out var typeLoop)) { var end = nextListRule++; var loop = nextListRule++; typeLoop = new[] { end, loop }; listRules.Add(new Rule( isListEnd: true, type: type, steps: list.Terminator == null ? new[] { new Step(itemSource, tag, false) } : new[] { new Step(itemSource, tag, false), new Step(separator, tag, true) } )); listRules.Add(new Rule( isListEnd: false, type: type, steps: separator == null ? new[] { new Step(itemSource, tag, false), new Step(typeLoop, tag, false) } : new[] { new Step(itemSource, tag, false), new Step(separator, tag, true), new Step(typeLoop, tag, false) })); _typeLoops.Add(loopKey, typeLoop); } // If the list has a minimum size, we need an initial segment before starting the loop, // e.g. FULL = LOOP LOOP LOOP LIST <-- a list of min size 4 (!) if (list.Min > 2) { var init = nextListRule++; var initSteps = new List <Step>(); for (var i = 1; i < list.Min; ++i) { initSteps.Add(new Step(itemSource, tag, false)); if (separator != null) { initSteps.Add(new Step(separator, tag, true)); } } initSteps.Add(new Step(typeLoop, tag, false)); listRules.Add(new Rule(isListEnd: false, type: type, steps: initSteps)); steps.Add(new Step(new[] { init }, tag, false)); } else if (list.Min == 2) { // typeLoop[1] is the loop rule "LOOP LIST" which always // contains at least two elements. So this is an optimization // to avoid creating an 'init' rule. steps.Add(new Step(new [] { typeLoop[1] }, tag, false)); } else { steps.Add(new Step(typeLoop, tag, false)); } continue; } throw new Exception($"Parameter {p.Name} in {m.Name}: nothing to do."); } return(new Rule(m, steps, args, uncompiledRule.Context ?? -1)); }
private bool Equals(TypeLoopKey other) => _separator == other._separator && _terminator == other._terminator && _type == other._type;
private bool Equals(TypeLoopKey other) { return(_separator == other._separator && _terminator == other._terminator && _type == other._type); }