/// <summary> /// Queries the RantDictionary according to the specified criteria and returns a random match. /// </summary> /// <param name="rng">The random number generator to randomize the match with.</param> /// <param name="query">The search criteria to use.</param> /// <param name="syncState">The state object to use for carrier synchronization.</param> /// <returns></returns> internal string Query(RNG rng, Query query, QueryState syncState) { RantDictionaryTable table; return !_tables.TryGetValue(query.Name, out table) ? "[Missing Table]" : table.Query(this, rng, query, syncState); }
private IEnumerable<Parselet> LeftAngle(Token<R> token) { Stringe name = null; if (reader.PeekToken().ID != R.DoubleColon) name = reader.ReadLoose(R.Text, "table name"); if (name != null && (Util.IsNullOrWhiteSpace(name?.Value) || !Util.ValidateName(name?.Value))) compiler.SyntaxError(token, "Invalid table name in query"); query = new Query { ClassFilter = new ClassFilter(), RegexFilters = new List<_<bool, Regex>>(), Carrier = new Carrier(), Name = name?.Value }; var exclusivity = reader.PeekToken(); if (exclusivity == null) compiler.SyntaxError(token, "Unexpected end of file"); if (exclusivity.ID == R.Dollar) { query.Exclusive = true; reader.ReadToken(); } while (true) // looks kinda dangerous { var queryReadToken = reader.ReadToken(); // egh switch (queryReadToken.ID) { default: compiler.SyntaxError(queryReadToken, $"Invalid token in query: '{queryReadToken.Value}'"); break; case R.RightAngle: RightAngle(queryReadToken, queryReadToken); yield break; case R.Subtype: Subtype(); break; case R.Hyphen: Hyphen(queryReadToken); break; case R.LeftParen: LeftParen(queryReadToken); break; case R.Question: case R.Without: RegexFilter(queryReadToken); break; case R.DoubleColon: DoubleColon(queryReadToken); break; } } }
internal string Query(RantDictionary dictionary, RNG rng, Query query, QueryState syncState) { var index = String.IsNullOrEmpty(query.Subtype) ? 0 : GetSubtypeIndex(query.Subtype); if (index == -1) return "[Bad Subtype]"; var pool = query.ClassFilter.IsEmpty ? _entries : _entries.Where(e => query.ClassFilter.Test(e, query.Exclusive)); if (_hidden.Any()) { var hide = _hidden.Where(hc => !query.ClassFilter.AllowsClass(hc)) .Except(dictionary.IncludedHiddenClasses); pool = pool.Where(e => !hide.Any(e.ContainsClass)); } if (query.RegexFilters.Any()) pool = query.RegexFilters.Aggregate(pool, (current, regex) => current.Where(e => regex.Item1 == regex.Item2.IsMatch(e.Terms[index].Value))); if (query.SyllablePredicate != null) pool = pool.Where(e => query.SyllablePredicate.Test(e.Terms[index].SyllableCount)); if (!pool.Any()) return MissingTerm; return syncState.GetEntry(query.Carrier, index, pool, rng)?[index] ?? MissingTerm; }
private RantAction Read(ReadType type, Token<R> fromToken = null) { // Stores actions for a single block item, argument, etc. var actions = new List<RantAction>(); // Stores sequences of actions for blocks, function calls, etc. var sequences = new List<RantAction>(); List<_<int, RantAction>> dynamicWeights = null; List<_<int, double>> constantWeights = null; Token<R> token = null; // ok let's do this while (!_reader.End) { token = _reader.ReadToken(); switch (token.ID) { case R.EOF: goto done; // Escape sequence case R.EscapeSequence: actions.Add(new RAEscape(token)); break; case R.LeftSquare: { var tagToken = _reader.ReadToken(); switch (tagToken.ID) { case R.Text: { string name = tagToken.Value; var group = RantFunctions.GetFunctionGroup(name); if (group == null) SyntaxError(tagToken, $"Unknown function: '{name}'"); var argList = new List<RantAction>(); if (_reader.TakeLoose(R.Colon)) { _funcCalls.Push(group); actions.Add(Read(ReadType.FuncArgs, token)); } else { var end = _reader.Read(R.RightSquare); actions.Add(new RAFunction(Stringe.Range(token, end), GetFunctionInfo(group, 0, token, end), argList)); } break; } case R.Regex: { try { _regexes.Push(Util.ParseRegex(tagToken.Value)); } catch (Exception e) { SyntaxError(tagToken, e); } _reader.ReadLoose(R.Colon); actions.Add(Read(ReadType.ReplacerArgs, token)); break; } case R.At: { actions.Add(_expressionCompiler.Read()); break; } case R.Dollar: { bool call = false; var nextToken = _reader.ReadToken(); // if the first token isn't a square, it's the name of the subroutine call if (nextToken.ID != R.LeftSquare) call = true; // read the name for the subroutine definition else nextToken = _reader.Read(R.Text, "subroutine name"); // if there's a token here, there's args var hasArgs = _reader.Take(R.Colon); if (call) { _subroutines.Push(new RACallSubroutine(nextToken)); actions.Add(Read(ReadType.SubroutineArgs, token)); break; } // this is a subroutine definition var subroutine = new RADefineSubroutine(nextToken); subroutine.Parameters = new Dictionary<string, SubroutineParameterType>(); // read all the args // right now we're at [$[name: or [$[name while (hasArgs && !_reader.End) { var nameToken = _reader.ReadLooseToken(); if (nameToken.ID == R.Text) subroutine.Parameters.Add(nameToken.Value, SubroutineParameterType.Greedy); else if (nameToken.ID == R.At) { nameToken = _reader.ReadLoose(R.Text); subroutine.Parameters.Add(nameToken.Value, SubroutineParameterType.Loose); } else SyntaxError(nameToken, "Expected subroutine parameter."); nextToken = _reader.ReadLooseToken(); if (nextToken.ID == R.Semicolon) continue; if (nextToken.ID == R.RightSquare) break; SyntaxError(nextToken, "Unexpected token in subroutine parameter definition."); } // if there's no args, we need to get rid of the right square before the body if(!hasArgs) _reader.ReadLoose(R.RightSquare); // start reading the body _reader.ReadLoose(R.Colon); subroutine.Body = Read(ReadType.SubroutineBody, token); actions.Add(subroutine); break; } default: SyntaxError(tagToken, "Expected function name, subroutine, or regex."); break; } break; } case R.RightSquare: { if ( !(type == ReadType.FuncArgs || type == ReadType.ReplacerArgs || type == ReadType.SubroutineBody || type == ReadType.SubroutineArgs)) Unexpected(token); // Add item to args sequences.Add(actions.Count == 1 ? actions[0] : new RASequence(actions, token)); switch (type) { case ReadType.SubroutineBody: return new RASequence(sequences, token); case ReadType.FuncArgs: { return new RAFunction(Stringe.Range(fromToken, token), GetFunctionInfo(_funcCalls.Pop(), sequences.Count, fromToken, token), sequences); } case ReadType.ReplacerArgs: if (sequences.Count != 2) SyntaxError(Stringe.Between(token, fromToken), "Replacer must have two arguments."); return new RAReplacer(Stringe.Range(fromToken, token), _regexes.Pop(), sequences[0], sequences[1]); case ReadType.SubroutineArgs: var subroutine = _subroutines.Peek(); subroutine.Arguments = sequences // filter out empty sequences .Where(x => !(x is RASequence && (x as RASequence).Actions.Count == 0)).ToList(); if (subroutine is RACallSubroutine) return _subroutines.Pop(); break; } break; } // Argument separator case R.Semicolon: if (!(type == ReadType.FuncArgs || type == ReadType.ReplacerArgs || type == ReadType.SubroutineArgs)) goto default; // If it's a replacer, make sure there are no arguments already. if (type == ReadType.ReplacerArgs && sequences.Count == 1) SyntaxError(token, "Too many arguments in replacer."); // Add item to args sequences.Add(actions.Count == 1 ? actions[0] : new RASequence(actions, token)); actions.Clear(); _reader.SkipSpace(); break; // Block case R.LeftCurly: _reader.SkipSpace(); actions.Add(Read(ReadType.Block, token)); break; // Block item boundary case R.Pipe: case R.RightCurly: // Wrong mode? if (type != ReadType.Block) { // Throw an error if it's '}' if (token.ID == R.RightCurly) Unexpected(token); // If it's a separator, just print it. goto default; } // Add item to block sequences.Add(actions.Count == 1 ? actions[0] : new RASequence(actions, token)); if (token.ID == R.RightCurly) return new RABlock(Stringe.Range(fromToken, token), sequences, dynamicWeights, constantWeights); _reader.SkipSpace(); actions.Clear(); break; // Constant literals case R.ConstantLiteral: actions.Add(new RAText(token, Util.UnescapeConstantLiteral(token.Value))); break; //queries case R.LeftAngle: { Stringe name = null; if (_reader.PeekToken().ID != R.DoubleColon) name = _reader.ReadLoose(R.Text, "table name"); if (name != null && (Util.IsNullOrWhiteSpace(name?.Value) || !Util.ValidateName(name?.Value))) SyntaxError(token, "Invalid table name in query."); _query = new Query { ClassFilter = new ClassFilter(), RegexFilters = new List<_<bool, Regex>>(), Carrier = new Carrier(), Name = name?.Value }; var exclusivity = _reader.PeekToken(); if (exclusivity.ID == R.Dollar) { _query.Exclusive = true; _reader.ReadToken(); } actions.Add(Read(ReadType.Query, token)); } break; // query subtypes case R.Subtype: { if (type != ReadType.Query) goto default; var subtype = _reader.ReadLoose(R.Text, "subtype"); _query.Subtype = subtype.Value; } break; // query filters case R.Hyphen: { if (type != ReadType.Query) goto default; var filterParts = new List<ClassFilterRule>(); do { bool negative = _reader.Take(R.Exclamation); if (_query.Exclusive && negative) throw new RantCompilerException(_sourceName, token, "You can't define a negative class filter in an exclusive query."); var className = _reader.ReadLoose(R.Text, "class name"); filterParts.Add(new ClassFilterRule(className.Value, !negative)); } while (_reader.TakeLoose(R.Pipe)); _query.ClassFilter.AddRuleSwitch(filterParts.ToArray()); } break; // query regex filters case R.Without: case R.Question: { if (type != ReadType.Query) goto default; bool negative = token.ID == R.Without; var regexToken = _reader.ReadLoose(R.Regex, "regex"); ((List<_<bool, Regex>>)_query.RegexFilters) .Add(new _<bool, Regex>(!negative, Util.ParseRegex(regexToken.Value))); break; } // syllable range, block weight case R.LeftParen: { // Block weight if (type == ReadType.Block && !actions.Any()) { if (constantWeights == null) constantWeights = new List<_<int, double>>(); if (dynamicWeights == null) dynamicWeights = new List<_<int, RantAction>>(); var weightAction = Read(ReadType.BlockWeight, token); if (weightAction is RAText) // Constant { var strWeight = (weightAction as RAText).Text; double d; int i; if (!Double.TryParse(strWeight, out d)) { if (Util.ParseInt(strWeight, out i)) { d = i; } else { SyntaxError(weightAction.Range, $"Invalid weight value '{strWeight}' - constant must be a number."); } } if (d < 0) SyntaxError(weightAction.Range, $"Invalid weight value '{strWeight}' - constant cannot be negative."); constantWeights.Add(_.Create(sequences.Count, d)); } else // Dynamic { dynamicWeights.Add(_.Create(sequences.Count, weightAction)); } break; } if (type != ReadType.Query) goto default; var nextToken = _reader.ReadToken(); int firstNumber = -1; int secondNumber = -1; var range = new Range(null, null); if (nextToken.ID == R.Number) { Util.ParseInt(nextToken.Value, out firstNumber); nextToken = _reader.ReadToken(); } if (nextToken.ID == R.Hyphen) { var number = _reader.ReadToken(); // (num - num) if (number.ID == R.Number) { Util.ParseInt(number.Value, out secondNumber); range.Minimum = firstNumber; range.Maximum = secondNumber; } // (num -) else if (number.ID == R.RightParen && firstNumber != -1) range.Minimum = firstNumber; } // (- num), interpreted as -num else if (firstNumber < 0) range.Maximum = Math.Abs(firstNumber); // (num) else if (firstNumber != -1) range.Maximum = range.Minimum = firstNumber; if (range.Minimum == null && range.Maximum == null) throw new RantCompilerException(_sourceName, token, "Unknown syllable range syntax."); _query.SyllablePredicate = range; break; } case R.RightParen: { if (type != ReadType.BlockWeight) goto default; _reader.SkipSpace(); if (!actions.Any()) SyntaxError(token, "Expected weight value."); return actions.Count == 1 && actions[0] is RAText ? actions[0] : new RASequence(actions, token); } // query carriers case R.DoubleColon: { if (type == ReadType.QueryCarrier) SyntaxError(token, "Unexpected carrier operator."); if (type != ReadType.Query) goto default; return Read(ReadType.QueryCarrier, token); } // match carrier case R.Equal: { if (type != ReadType.QueryCarrier) goto default; var name = _reader.ReadLoose(R.Text, "carrier match identifier"); _query.Carrier.AddComponent(CarrierComponent.Match, name.Value); } break; // associative, disassociative, divergent, relational, or the match versions of those case R.At: { if (type != ReadType.QueryCarrier) goto default; // 0 = associative, 1 = disassociative, 2 = divergent, 3 = relational var componentType = CarrierComponent.Associative; var nextToken = _reader.PeekToken(); // disassociative switch (nextToken.ID) { case R.Exclamation: componentType = CarrierComponent.Dissociative; _reader.ReadToken(); break; case R.Plus: componentType = CarrierComponent.Divergent; _reader.ReadToken(); break; case R.Question: componentType = CarrierComponent.Relational; _reader.ReadToken(); break; } // match if (_reader.PeekToken().ID == R.Equal) { _reader.ReadToken(); if (componentType == CarrierComponent.Associative) componentType = CarrierComponent.MatchAssociative; if (componentType == CarrierComponent.Dissociative) componentType = CarrierComponent.MatchDissociative; if (componentType == CarrierComponent.Divergent) componentType = CarrierComponent.MatchDivergent; if (componentType == CarrierComponent.Relational) componentType = CarrierComponent.MatchRelational; } var name = _reader.ReadLoose(R.Text, "carrier association identifier").Value; _query.Carrier.AddComponent(componentType, name); } break; // unique or match-unique case R.Exclamation: { if (type != ReadType.QueryCarrier) goto default; bool match = false; if (_reader.PeekToken().ID == R.Equal) { match = true; _reader.ReadToken(); } var name = _reader.ReadLoose(R.Text, "carrier unique identifier").Value; var componentType = (match ? CarrierComponent.MatchUnique : CarrierComponent.Unique); _query.Carrier.AddComponent(componentType, name); } break; // rhyme case R.Ampersand: { if (type != ReadType.QueryCarrier) goto default; var name = _reader.ReadLoose(R.Text, "carrier rhyme identifier").Value; _query.Carrier.AddComponent(CarrierComponent.Rhyme, name); } break; // end of queries case R.RightAngle: if (type != ReadType.Query && type != ReadType.QueryCarrier) SyntaxError(token, "Unexpected query terminator."); if (_query.Name == null && _query.Carrier.GetTotalCount() == 0) SyntaxError(token, "Carrier delete query specified without any carriers."); return new RAQuery(_query, Stringe.Between(fromToken, token)); // Plain text case R.Whitespace: { switch (_reader.PeekType()) { case R.EOF: case R.RightSquare: case R.RightAngle: case R.RightCurly: continue; case R.Pipe: if (type == ReadType.Block) continue; break; case R.Semicolon: switch (type) { case ReadType.FuncArgs: case ReadType.ReplacerArgs: case ReadType.SubroutineArgs: continue; } break; } actions.Add(new RAText(token)); break; } default: if (type == ReadType.QueryCarrier) SyntaxError(token, "Expected query carrier."); actions.Add(new RAText(token)); break; } } done: switch (type) { case ReadType.Sequence: return new RASequence(actions, token); case ReadType.Block: throw new RantCompilerException(_sourceName, fromToken, "Unterminated block found."); default: throw new RantCompilerException(_sourceName, token, "Unexpected end of file."); } }