diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..41ee56c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +gen/* +BisonReport.txt +ifpp.exe \ No newline at end of file diff --git a/doc/ifpp-manual.html b/doc/ifpp-manual.html new file mode 100644 index 0000000..f464194 --- /dev/null +++ b/doc/ifpp-manual.html @@ -0,0 +1,697 @@ + + + + + +IFPP - Item Filter Preprocessor, version 1.0.0 + + + + + + + + + + + + + + + + +

IFPP - Item Filter Preprocessor, version 1.0.0

+ + + + + + + + + + + + + + + +

Table of Contents

+ +
+ + +
+ + + +
+

+Next: , Up: (dir)   [Contents][Index]

+
+ +

Manual for IFPP - Item Filter Preprocessor

+ +

This is the manual for IFPP - Item Filter Preprocessor (version 1.0.0) for Path of Exile. +This version of IFPP is designed to be compatible with Path of Exile version 3.4.x, although will probably work with later versions of PoE as well. +

+

Created by Abdiel Kavash, 2018. +

+ + + + + + + + + + + + + + + + +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ +

1 Introduction

+ +

IFPP is a preprocessor for item filters for Path of Exile, an action RPG developed by Grinding Gear Games. This program allows item filter creators to use a much more powerful syntax than that of native item filters provided by GGG. Instead of comparing items with one rule at a time, and stopping immediately when a matching rule is found; an IFPP filter continues looking for more rules that can add further styles to the display of dropped items. +

+

For example, let’s say you want to display normal items as white, magic items as blue, rare yellow, and unique orange. But you also want to add a white border around all items with linked red, green, and blue sockets for the Chromatic Orb vendor recipe. Using the native item filter syntax provided by GGG, you need a separate rule for each rarity both with and without the RGB socket group: +

+
+
Show
+    Rarity Normal
+    SocketGroup RGB
+    SetTextColor 200 200 200
+    SetBorderColor 255 255 255
+
+Show
+    Rarity Normal
+    SetTextColor 200 200 200
+
+Show
+    Rarity Magic
+    SocketGroup RGB
+    SetTextColor 136 136 255
+    SetBorderColor 255 255 255
+
+Show
+    Rarity Magic
+    SetTextColor 136 136 255
+
+Show
+    Rarity Rare
+    SocketGroup RGB
+    SetTextColor 255 255 119
+    SetBorderColor 255 255 255
+
+Show
+    Rarity Rare
+    SetTextColor 255 255 119
+
+Show
+    Rarity Unique
+    SocketGroup RGB
+    SetTextColor 175 96 37
+    SetBorderColor 255 255 255
+
+Show
+    Rarity Unique
+    SetTextColor 175 96 37
+
+ +

If you want to add more and more independent conditions, you need to add a new rule for every possible combination. This results in large amounts of reused code, poor readability, and filters which are difficult to maintain and modify. For example, if you wanted to change the color of the border for RGB items, you would have to change it in all of the rules. +

+

With IFPP, you can write the same filter like this: +

+
+
RULE
+    Rarity Normal
+    SetTextColor 200 200 200
+
+RULE
+    Rarity Magic
+    SetTextColor 136 136 255
+
+RULE
+    Rarity Rare
+    SetTextColor 255 255 119
+
+RULE
+    Rarity Unique
+    SetTextColor 175 96 37
+
+RULE
+    SocketGroup RGB
+    APPEND SetBorderColor 255 255 255
+
+ +

The APPEND keyword in the last rule tells IFPP to add the border color to all matching items, together with any other styles that have been already applied to them above. +

+

In addition to this powerful functionality, IFPP provides several quality of life improvements over GGG native filters, including reusing values and code using variables, incremental rules, “syntactic sugar” like hexadecimal values for colors, and more. +

+

This manual assumes the reader has a basic knowledge of writing item filters for PoE. For more information on item filters, see the item filter guide at the unofficial PoE wiki. +

+

Version 1.0.0 of IFPP is designed to be fully compatible with Path of Exile version 3.4.x, although the created filters will most likely function correctly in more recent versions of PoE as well. See Compatibility with Different Versions for details. +

+ + + +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ + +

2 Using IFPP

+ +

IFPP works as a preprocessor. The program takes a filter using the enhanced IFPP syntax as an input, and creates from it an item filter executing the same rules using only the native GGG syntax that can be read by Path of Exile. From here on, we will always refer to filters with the added features of IFPP as “IFPP filters”, and to filters using only the standard syntax provided by GGG as “native filters”. +

+

The workflow of using IFPP looks like this: +

+
    +
  1. Write your IFPP filter using the enhanced syntax options described in this manual. +
  2. Run the IFPP preprocessor on this filter to generate a native filter. +
  3. Copy the created native filter into the usual location for item filters (\My Documents\My Games\Path of Exile\). +
  4. Load your filter in PoE and play! +
+ +

Currently, IFPP is only available as a command line utility (GUI version to come soon). The syntax is the following: +

+
+
ifpp <input file> [output file] [log file]
+
+ +

The input file is the IFPP filter you have created, and has by convention the extension .ifpp. The output file is the native filter that will be created by IFPP. Use the extension .filter for it to be usable by Path of Exile. The optional log file lists possible problems with your IFPP filter. +

+

If you do not specify either the output or the log file, the same file name as the input (with its extension stripped) will be used for both, with the extensions .filter and .log respectively. If you use "-" in place of either file name, it will be written to the console instead. +

+ + +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ + +

3 IFPP Syntax

+ +

This section describes the syntax of IFPP filter files. +

+

Contents: +

+ + + + + + + + +
+ +
+

+Next: , Up: IFPP Syntax   [Contents][Index]

+
+ +

3.1 Basics

+ +

The IFPP filter syntax largely resembles the syntax of native PoE filters. It also consists of a series of rules, with conditions describing the items to be matched and actions applying visual styles to them. In fact, every native filter is also a valid IFPP filter, and will produce the same filter (up to formatting differences) as an output if ran through IFPP. However, in addition to this, IFPP also offers many new syntax features extending the native filters. +

+

IFPP filters are always case-insensitive. Writing RULE APPEND is the same as Rule Append or rule append and so on. In this manual, we will use ALL CAPS for IFPP-specific syntax, in order to clearly distinguish it from keywords used by native filters. However, you should not feel restricted by this: if you prefer writing your keywords with a proper capitalization, you are free to do so (and in fact the author writes his filters in this way). +

+

Horizontal whitespace (spaces and tabs) never matters in IFPP filters. You are free to indent your filters in whatever way you wish. However, vertical whitespace (new lines) does: every IFPP instruction must be written on a separate line, and blank lines have a special meaning in some cases. A line containing only whitespace is considered blank; but a line containing a comment is not. +

+

An IFPP filter consists of three different types of commands: rules, variable definitions, and special instructions. +Rules form the core of an item filter. Similarly to native filters, a rule defines conditions on items to match, and actions to perform for all matched items. +Variables can store values (such as numbers or item names) which can then be referenced later in the filter. +Special instructions affect the behavior of IFPP in various ways. +

+
+ +
+

+Next: , Previous: , Up: IFPP Syntax   [Contents][Index]

+
+ + +

3.2 Comments

+ +

IFPP offers three different styles of comments: +

+
+
# Native filter style single line comments.
+
+ +
+
// C-style single line comments.
+
+ +
+
/* C++ - style
+multi-line
+comments. */
+
+ +

Comments are in all cases ignored and have no impact on (and will not be output to) the final native filter. +

+
+ +
+

+Next: , Previous: , Up: IFPP Syntax   [Contents][Index]

+
+ + + +

3.3 Rules

+ +

Rule sections make up the core part of an IFPP filter. Just like in a native filter, a rule consists of several conditions, which specify items that should be matched by this rule; and actions which specify what to do with the matching items - usually apply some visual style. Unlike in native filters, however, processing an item does not end with the first matching rule. Subsequent rules can change and override styles that were applied to an item by previous rules. The various ways in which rules affect each other are specified by modifiers. +

+ + + + + + + + + + +
+ +
+

+Next: , Up: Rules   [Contents][Index]

+
+ +

3.3.1 Basic Syntax

+ +

The basic syntax of writing a rule is the following: +

+
+
RULE [modifiers] {
+    [conditions]
+    [actions]
+}
+
+ +

The opening brace can be on the same line as the RULE header, or on the line immediately following it. You can omit the braces, in this case the rule contains all commands until the next blank line. (A line containing only whitespace is considered blank, but a line containing a comment is not.) If the rule has no modifiers, the entire header can also be omitted. The modifier APPEND is assumed in this case. See Modifiers for details. +

+

This means that all of the following are valid rules which produce the same output: +

+
+
RULE APPEND {
+    BaseType "Exalted Orb"
+    PlayAlertSound 8 200
+}
+
+ +
+
RULE APPEND {
+    BaseType "Exalted Orb"
+
+    PlayAlertSound 8 200
+}
+
+ +
+
RULE APPEND
+{
+    BaseType "Exalted Orb"
+    PlayAlertSound 8 200
+}
+
+ +
+
RULE APPEND
+    BaseType "Exalted Orb"
+    PlayAlertSound 8 200
+
+ +
+
RULE APPEND
+    BaseType "Exalted Orb"
+    # Money!
+    PlayAlertSound 8 200
+
+ +
+
BaseType "Exalted Orb"
+PlayAlertSound 8 200
+
+ +

Again, keep in mind that if you do not use braces, the rule only takes commands until the next blank line! The following are two separate rules, which is probably not what you want: +

+
+
RULE
+    BaseType "Exalted Orb"
+
+    PlayAlertSound 8 200
+
+ +

A rule can be defined with no conditions; in this case the rule applies to all items. You can use this, for example, to hide all items not matched by the rest of your filter. A rule with no actions is also valid, although such a rule will usually not do anything. +

+

Finally, instead of the RULE keyword, a rule can also begin with either SHOW or HIDE. The keyword SHOW is equivalent to RULE APPEND FINAL, and HIDE is equivalent to RULE APPEND FINAL with an additional action HIDDEN added at the end. However, this is purely for compatibility with native filters. Some special features of IFPP might not work in the expected way inside of rules declared with SHOW or HIDE. Therefore, if you are writing an IFPP filter from scratch, do not use the keywords SHOW or HIDE like this. Only use RULE to declare a rule. +

+ + +
+ +
+

+Next: , Previous: , Up: Rules   [Contents][Index]

+
+ + +

3.3.2 Conditions

+ +

Just like in native filters, the conditions of a rule specify which items the rule should be applied to. Version 1.0.0 of IFPP supports all conditions that can be used in native filters as of PoE version 3.4.x. Namely, here is the full list of supported conditions: +

+
+
ItemLevel
+DropLevel
+Quality
+Sockets
+LinkedSockets
+Height
+Width
+StackSize
+GemLevel
+MapTier
+
+Class
+BaseType
+HasExplicitMod
+
+Identified
+Corrupted
+ElderItem
+ShaperItem
+ShapedMap
+
+Rarity
+SocketGroup
+
+ +

The syntax of all conditions is the same as in native filters. Notably, for conditions that take a list of strings (Class, BaseType, and HasExplicitMod), every string is matched separately. So Class One Hand will match any item with either “One” or “Hand” in its class. To match items with the class “One Hand”, use quotes: Class "One Hand", just like you would do in a native filter. Also as in native filters, a rule with multiple conditions will only match items which satisfy all of the conditions. +

+

Conditions which make a numerical comparison (in the first group listed above) will perform a bounds check on the value given. A value that is out of the possible range for a condition, such as Sockets > 8, results in a warning. +

+ + +
+ +
+

+Next: , Previous: , Up: Rules   [Contents][Index]

+
+ +

3.3.3 Actions

+ +

The actions of a rule specify what to do with the items that match the rule. Most of the time, this changes the way the label of the item is displayed. All the actions used by native filters as of PoE version 3.4.x are supported, namely: +

+
+
SetTextColor
+SetBackgroundColor
+SetBorderColor
+
+SetFontSize
+
+PlayAlertSound
+PlayAlertSoundPositional
+
+CustomAlertSound
+MinimapIcon
+PlayEffect
+DisableDropSound
+
+ +

The syntax of all actions is the same as in native filters. Actions which take a numerical argument perform a bounds check. If a value is out of the possible range for an action, IFPP gives a warning and adjusts the value to fit within the allowed bounds. +

+

In addition to these, IFPP provides two new actions: +

+
+
HIDDEN [true/false]
+
+ +

When set to true, this action behaves just like a Hide block in a native filter: it makes the item not display a label at all. However, all other actions in the rule are still applied to the item. Sounds, minimap icons, and light beams still work; and the label will be displayed with the proper style when the “Highlight Items” key is held. The value of the HIDDEN action can also be overridden by later rules, just like any other action! +

+

Note that the spelling of the action is HIDDEN, not Hide as in native filters. There are unfortunate technical reasons that prevent IFPP from using the same keyword. +

+
+
USESTYLE [variable]
+
+ +

This action adds all actions defined in the given variable to the rule. The variable must be of the type STYLE, otherwise IFPP gives an error and this action is ignored. +

+
+ +
+

+Next: , Previous: , Up: Rules   [Contents][Index]

+
+ +

3.3.4 Modifiers

+ +
+ +
+

+Previous: , Up: Rules   [Contents][Index]

+
+ +

3.3.5 Incremental Rules

+ + +
+ +
+

+Next: , Previous: , Up: IFPP Syntax   [Contents][Index]

+
+ +

3.4 Variables

+ +
+ +
+

+Previous: , Up: IFPP Syntax   [Contents][Index]

+
+ +

3.5 Special Instructions

+ +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ +

4 A Complete Example

+ +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ +

5 IFPP Errors

+ +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ +

6 Performance Considerations

+ +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ +

7 Compatibility with Native Filters

+ +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ +

8 Compatibility with Different Versions

+ +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ +

9 Version History

+ +
+ +
+

+Next: , Previous: , Up: Top   [Contents][Index]

+
+ +

10 Contact

+ +
+ +
+

+Previous: , Up: Top   [Contents][Index]

+
+ +

Index

+ +
Jump to:   C +   +I +   +R +   +U +   +
+ + + + + + + + + + + + + + + + + +
Index Entry  Section

C
Comments: Comments
Conditions: Conditions

I
IFPP syntax: IFPP Syntax

R
RULE: Rules
Rules: Rules

U
Using IFPP: Using IFPP

+
Jump to:   C +   +I +   +R +   +U +   +
+ +
+ + + + + diff --git a/makefile b/makefile new file mode 100644 index 0000000..6c0cb6e --- /dev/null +++ b/makefile @@ -0,0 +1,76 @@ +GenDir = gen +SrcDir = src +TestDir = tests + +GenClass = Lexer Parser +SrcClass = Types Logger Context ParserWrapper +#ifppDriver ifppRuleOperations ifppCompiler + +TestParse = basicSyntax +TestCompile = overrideWithoutChange overrideSameAction overrideNumeric nameLists incremental +AllTests = $(TestParse) $(TestCompile) + +GenObj = $(addprefix $(GenDir)/,$(addsuffix .o,$(GenClass))) +SrcObj = $(addprefix $(GenDir)/,$(addsuffix .o,$(SrcClass))) +AllObjs = $(GenObj) $(SrcObj) + +Flex = flex -i +Bison = bison +Gcc = g++ -Wall -Wextra -pedantic -I src -I gen +GccStrict = g++ -Wall -Wextra -pedantic -Weffc++ -Werror -I src -I gen + +.PHONY: lex parser analyze tests doc clean + +ifpp: $(AllObjs) src/ifpp.cpp + $(GccStrict) -o ifpp $(AllObjs) src/ifpp.cpp + +lex: $(GenDir)/Lexer.cpp $(GenDir)/Lexer.h + +parser: $(GenDir)/Parser.cpp $(GenDir)/Parser.h + +analyze: src/Parser.y + $(Bison) -Wall --report=all --report-file=BisonReport.txt $< + +tests: $(AllTests) + +doc: doc/ifpp-manual.html + +clean: + -rm gen/* + -rm tests/*.parsed.ifpp + -rm tests/*.partial.ifpp + -rm tests/*.filter + -rm ifpp.exe + + + +$(GenDir)/Lexer.cpp $(GenDir)/Lexer.h: $(SrcDir)/Lexer.l $(SrcDir)/Types.h $(GenDir)/Parser.h + $(Flex) --outfile="gen/Lexer.cpp" --header-file="gen/Lexer.h" $(SrcDir)/Lexer.l + +$(GenDir)/Parser.cpp $(GenDir)/Parser.h $(GenDir)/location.hh: $(SrcDir)/Parser.y $(SrcDir)/Types.h $(SrcDir)/Context.h + $(Bison) --output="gen/Parser.cpp" --defines="gen/Parser.h" $(SrcDir)/Parser.y + +$(GenObj): $(GenDir)/%.o: $(GenDir)/%.cpp $(SrcDir)/Types.h $(SrcDir)/Logger.h + $(Gcc) -c -o $@ $< + +$(SrcObj): $(GenDir)/%.o: $(SrcDir)/%.cpp $(SrcDir)/%.h $(SrcDir)/Types.h + $(GccStrict) -c -o $@ $< + +$(GenDir)/Context.o: $(SrcDir)/Logger.h + + + +$(TestParse): %: $(TestDir)/%.parsed.ifpp + +$(TestCompile): %: $(TestDir)/%.filter + +$(TestDir)/%.parsed.ifpp: $(TestDir)/%.ifpp ifpp + ./ifpp -c -Dpartial -DparseOnly $< + +$(TestDir)/%.filter: $(TestDir)/%.ifpp ifpp + ./ifpp -c -Dpartial $< + +doc/ifpp-manual.html: src/ifpp-manual.texinfo + makeinfo --html --no-split --css-include="src/ifpp-manual.css" -o "doc/ifpp-manual.html" "src/ifpp-manual.texinfo" + + diff --git a/src/Compiler.cpp b/src/Compiler.cpp new file mode 100644 index 0000000..dd4e20c --- /dev/null +++ b/src/Compiler.cpp @@ -0,0 +1,537 @@ +#include "Compiler.h" +#include "RuleOperations.h" +#include +#include +#include +#include + +namespace ifpp { + +std::ostream & RuleNative::printSelf(std::ostream & os, PrintStyle ps) const { + switch (ps) { + case PRINT_NATIVE: { + if (useless) { + throw InternalError("Writing a useless rule to native filter!", __FILE__, __LINE__); + } + + auto it = actions.find("Hidden"); + if (it != actions.end() && static_cast(it->second)->par1) { + os << "Show" << std::endl; + } else { + os << "Hide" << std::endl; + } + break; + } + case PRINT_IFPP: + os << '[' << guid << "] Rule "; + if (useless) os << "USELESS "; + print(os, ps, modifiers) << '{' << std::endl; + break; + + default: + throw UnhandledCase("Print style", __FILE__, __LINE__); + } + + ++IFPP_TABS; + for (const auto & c : conditions) { + print(os, ps, c.second); + } + for (const auto & a : actions) { + print(os, ps, a.second); + } + --IFPP_TABS; + + if (ps == PRINT_IFPP) { + os << '}' << std::endl; + } + return os; +} + +bool RuleNative::hasMod(ModifierList ml) const { + return modifiers & ml; +} + +RuleNative::~RuleNative() { + for (auto & c : conditions) delete c.second; + for (auto & a : actions) delete a.second; +} + +/* +Always adds a clone of c, if necessary. +*/ +void RuleNative::addCondition(const Condition * c) { + auto cOld = conditions.find(c->what); + if (cOld == conditions.end()) { + // We do not have this type of condition yet. + // Check if the condition matches anything. + // We still add it though. + switch (c->conType) { + case CON_INTERVAL: { + auto ci = static_cast(c); + if (ci->from > ci->to) useless = true; + break; + } + case CON_SOCKETGROUP: { + auto csg = static_cast(c); + if (csg->socketGroup.r + csg->socketGroup.g + csg->socketGroup.b + csg->socketGroup.w > getLimit("LinkedSockets", MAX)) { + useless = true; + } + break; + } + case CON_BOOL: // Always matches something. + break; + case CON_NAMELIST: // We can not determine what this matches. + break; + default: + throw UnhandledCase("Condition type", __FILE__, __LINE__); + } + // Add the condition and take ownership. + conditions.insert(std::make_pair(c->what, static_cast(c->clone()))); + return; + } + + // A condition of this type already exists. Intersect with it. + switch (c->conType) { + case CON_INTERVAL: { + // Refine the interval in existing condition. + auto c1 = static_cast(cOld->second); + auto c2 = static_cast(c); + + if (c1->from < c2->from) c1->from = c2->from; + if (c1->to > c2->to) c1->to = c2->to; + if (c1->from > c1->to) useless = true; + break; + } + case CON_BOOL: { + // A condition can not be true and false at the same time. + auto c1 = static_cast(cOld->second); + auto c2 = static_cast(c); + + if (c1->value != c2->value) useless = true; + break; + } + case CON_SOCKETGROUP: { + auto c1 = static_cast(c); + int r = c1->socketGroup.r; + int g = c1->socketGroup.g; + int b = c1->socketGroup.b; + int w = c1->socketGroup.w; + + bool add = true; + auto range = conditions.equal_range(c->what); + for (auto it = range.first; it != range.second; ) { + // Check if we need more than 6 different sockets. + auto c2 = static_cast(it->second); + if (c2->socketGroup.r > r) r = c2->socketGroup.r; + if (c2->socketGroup.g > g) g = c2->socketGroup.g; + if (c2->socketGroup.b > b) b = c2->socketGroup.b; + if (c2->socketGroup.w > w) w = c2->socketGroup.w; + + // If some existing condition is stricter than the new condition, we do not need to do anything. + if (ConditionSubset(c2, c1)) { + add = false; + break; + } + // If the new condition is stricter than some existing condition, we can remove the existing one. + if (ConditionSubset(c1, c2)) { + delete it->second; + it = conditions.erase(it); + } else { + ++it; + } + } + + // Check if we need more than 6 different sockets. + if (r + g + b + w > getLimit("SocketGroup", MAX)) useless = true; + + // Add the new condition. + if (add) { + conditions.insert(std::make_pair(c->what, static_cast(c->clone()))); + } + break; + } + case CON_NAMELIST: { + // There can be more than one of these conditions, in case of intersections. + auto c1 = static_cast(c); + + bool add = true; + auto range = conditions.equal_range(c->what); + for (auto it = range.first; it != range.second; ) { + auto c2 = static_cast(it->second); + + // If some existing condition is stricter than the new condition, we do not need to do anything. + if (ConditionSubset(c2, c1)) { + add = false; + break; + } + // If the new condition is stricter than some existing condition, we can remove the existing one. + if (ConditionSubset(c1, c2)) { + delete it->second; + it = conditions.erase(it); + } else { + ++it; + } + } + + // Add the new condition. + if (add) { + conditions.insert(std::make_pair(c->what, static_cast(c->clone()))); + } + break; + } + default: + throw UnhandledCase("Condition type", __FILE__, __LINE__); + } +} + +/* +Adds a clone of the action. +Does NOT take modifiers into account. This should be handled outside. + +TODO: possibly only change the parameters of an action when replacing it? +*/ +void RuleNative::addAction(const Action * a) { + // Possibly override other actions of the same type. + auto it = actions.find(a->what); + if (it != actions.end()) { + delete it->second; + it->second = static_cast(a->clone()); + } else { + actions.insert(std::make_pair(a->what, static_cast(a->clone()))); + } +} + +RuleNative * RuleNative::clone() const { + // We do not need to do checking, just copy the conditions and actions. + // Beware, this might break if addCondition or addAction start to do other things. + RuleNative * r = new RuleNative(modifiers); + r->useless = useless; + + for (const auto & c : conditions) { + r->conditions.insert(std::make_pair(c.first, static_cast(c.second->clone()))); + } + for (const auto & a : actions) { + r->actions.insert(std::make_pair(a.first, static_cast(a.second->clone()))); + } + + return r; +} + +std::ostream & print(std::ostream & os, PrintStyle ps, const FilterNative & f) { + for (size_t i = 0; i < f.size(); ++i) { + if (i) os << std::endl; + print(os, ps, f[i]); + } + return os; +} + +/* +Adds an IFPP rule (with potential sub-rules) to a native filter. +This rule will be *intersected* with all rules already in the filter, +so we do not want to do this with top-level rules! +*/ +static void AddRuleRecursive(FilterNative & filter, RuleNative * base, RuleIFPP * rule) { + for (const auto & c : rule->commands) { + switch (c->comType) { + case COM_CONDITION: + base.addCondition(static_cast(c)); + break; + case COM_ACTION: + base.addAction(static_cast(c)); + break; + case COM_RULE: { + RuleNative b = base->clone(); + AddRuleRecursive(filter, b, static_cast(c)); + delete b; + break; + case COM_DEFAULT: + // TODO: This should only do stuff for non-intersecting conditions. + for (const auto & a : static_cast(c)->style) { + base.addAction(a); + } + break; + default: + throw UnhandledCase("Command type", __FILE__, __LINE__); + } + } + + // TODO: WE ARE HERE. ADD THE RULE AND INTERSECT WITH ALL ACTIONS. +} + +static void AddRule(FilterNative & filter, RuleIFPP * rule) { + FilterNative temp; + RuleNative r = new RuleNative(rule->modifiers); + AddRuleRecursive(temp, r, rule); + delete r; + filter.insert(filter.end(), temp.begin(), temp.end(); +} + + + + + +/* +Processes an incremental rule. +The rule first is modified by the rule second: + +Conditions of the same type are overwritten. +Conditions which are only in first are retained. +Conditions only in second are added. + +Actions - same. + +All conditions and actions in the second rule are deleted! +*/ +static void IncrementRule(RuleNative * first, RuleNative * second) { + for (const auto & c : second->conditions) { + // Delete all matching conditions from first. + // Caution, need to call destructors properly. + const auto & r = first->conditions.equal_range(c.first); + for (auto it = r.first; it != r.second; ++it) { + delete it->second; + } + first->conditions.erase(r.first, r.second); + } + // We deleted some conditions, the rule is possibly not useless anymore. + first->useless = false; + + // Add all conditions and actions. + for (const auto & c : second->conditions) { + // Any conditions of the same type have been deleted. + first->addCondition(c.second); + delete c.second; + } + for (const auto & a : second->actions) { + // This overrides any actions of the same type. + first->addAction(a.second); + delete a.second; + } + + // Clean up the second rule. + second->conditions.clear(); + second->actions.clear(); +} + +/* +Processes a rule from input to get it ready for compilation. +This: +- TODO: splits incremental rules. +- Organizes conditions and actions into maps. +- Resolves duplicate conditions and actions. +-- Duplicate conditions are intersected. +-- Only the latter of duplicate actions is kept. +*/ +void preprocessRule(const Rule * rule, std::vector & rules) { + // Preprocess the rule into native rules. + + // Compatibility with native rules. Do not process incremental rules at all. + if (rule->hasMod(MOD_SHOW) || rule->hasMod(MOD_HIDE)) { + RuleNative * rn = new RuleNative(MOD_APPEND & MOD_FINAL); + + for (const auto & com : rule->commands) { + switch (com->comType) { + case COM_CONDITION: + // This only removes conditions that are a subset/superset of one another. + // We don't do intersections here. + rn->addCondition(static_cast(com)); + break; + + case COM_ACTION: + rn->addAction(static_cast(com)); + break; + + default: + throw InternalError("Unknown command type!", __FILE__, __LINE__); + } + } + + // Add action Hidden to native Hide rules. + // This action should not be Override (?) + if (rule->hasMod(MOD_HIDE)) rn->addAction(new ActionBool(0, "Hidden", true)); + + if (!rn->useless) { + rules.push_back(rn); + } else { + delete rn; + } + } + else { + // IFPP rule, possibly incremental. + RuleNative * fullRule = new RuleNative(rule->modifiers); + RuleNative * currentRule = new RuleNative(rule->modifiers); + + // Whether the last thing we have read was an action. + bool lastAction = false; + + for (const auto & com : rule->commands) { + switch (com->comType) { + case COM_CONDITION: + if (lastAction) { + // We have a piece of an incremental rule. + IncrementRule(fullRule, currentRule); + if (!fullRule->useless) { + rules.push_back(fullRule->clone()); + } + } + currentRule->addCondition(static_cast(com)); + lastAction = false; + break; + + case COM_ACTION: + currentRule->addAction(static_cast(com)); + lastAction = true; + break; + + default: + throw InternalError("Unknown command type!", __FILE__, __LINE__); + } + } + + // Add the last incremental section. + IncrementRule(fullRule, currentRule); + if (!fullRule->useless) { + rules.push_back(fullRule); // We do not need one more copy. + } else { + delete fullRule; + } + delete currentRule; + } +} + +/* +Appends all rules from the second filter to the first. Clears the second filter. +Currently there is no optimization being done between different sections - this could be added? +*/ +void AppendFilter(FilterNative & first, FilterNative & second) { + // first.reserve(first.size() + second.size()); // This is probably smart enough to not be needed? + first.insert(first.end(), second.begin(), second.end()); +} + +/* +Compiles a single IFPP rule and appends the native rules to a filter. +*/ +void Compiler::CompileRule(const RuleIFPP * rule, FilterNative & outFilter) { +} + +/* +Compiles an IFPP filter into a native filter. +*/ +void Compiler::Compile(const FilterIFPP & inFilter, FilterNative & outFilter) { + outFilter.clear(); + + FilterNative partFilter; + + for (auto statement : inFilter) { + switch (statement->stmType) { + case STM_DEFINITION: + // Nothing to do, variables are handled in the parser. + break; + + case STM_INSTRUCTION: + // Version is handled in the parser. // TODO + break; + + case STM_RULE: { + CompileRule(static_cast(statement), outFilter); + break; + + default: + throw UnhandledCase("Statement type", __FILE__, __LINE__); + + /* + rules.clear(); + preprocessRule(, rules); + + if (rules.size() == 0) { + if (writePartial) { + partialStream << "######################" << std::endl; + partialStream << "Rule [" << section->guid << "] skipped, matches nothing" << std::endl; + print(partialStream, PRINT_IFPP, section); + partialStream << "###########" << std::endl << std::endl; + } + } + else { + if (writePartial) { + partialStream << "######################" << std::endl; + partialStream << "Adding rule [" << section->guid << ']' << std::endl; + print(partialStream, PRINT_IFPP, section); + + if (rules.size() > 1) { + partialStream << "###########" << std::endl; + partialStream << "# Incremental rule, preprocessed into:" << std::endl; + partialStream << "###########" << std::endl << std::endl; + print(partialStream, PRINT_IFPP, rules); + } + partialStream << "###########" << std::endl << std::endl; + } + + static FilterNative temp; + temp.clear(); + temp.swap(partFilter); + + // TODO: comment this better. + for (auto & ruleOld : temp) { + for (auto & ruleNew : rules) { + if (ruleNew) { + RuleNative * top = RuleIntersection(ruleOld, ruleNew); + + if (!top) { + // There is no intersection. The rules don't interact at all. + // Save us some time by not having to compute differences. + continue; + } + + RuleNative * mid = RuleDifference(ruleOld, ruleNew); + RuleNative * bot = RuleDifference(ruleNew, ruleOld); + + if (!ruleOld->hasMod(MOD_FINAL) && top != ruleOld) { + // Add the rule for the intersection, since it is Overriding some parts of old. + partFilter.push_back(top); + + // Modify the old rule. + if (mid != ruleOld) { + delete ruleOld; + ruleOld = mid; + } + } + + if (bot != ruleNew) { + // Modify the new rule. + // Do this even if it is Final - Final only applies to following rules! + delete ruleNew; + ruleNew = bot; + } + + if (!ruleOld) { + // We have added all possible combinations with the new rule. We can stop here. + break; + } + } + } + + if (ruleOld) partFilter.push_back(ruleOld); + } + + for (auto & rule : rules) { + if (rule && !rule->hasMod(MOD_ADDONLY)) partFilter.push_back(rule); + } + + if (writePartial) { + print(partialStream, PRINT_IFPP, partFilter) << std::endl; + } + } + break; + + } + + default: + throw InternalError("Unknown section type!", __FILE__, __LINE__); + */ + } + } + + // Attach the rest of the filter since the last Flush. + AppendFilter(outFilter, partFilter); +} + +} \ No newline at end of file diff --git a/src/Compiler.h b/src/Compiler.h new file mode 100644 index 0000000..bd363c6 --- /dev/null +++ b/src/Compiler.h @@ -0,0 +1,54 @@ +#ifndef IFPP_COMPILER_H +#define IFPP_COMPILER_H + +#include "ifppTypes.h" +#include "ifppLogger.h" + +namespace ifpp { + +// Native rule, but keeps modifiers. +// Only one action per type allowed. +// No nested rules. +struct RuleNative { + RuleNative(ModifierList m) : modifiers(m), conditions(), actions(), useless(false), guid(IFPP_GUID()) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const; + bool hasMod(ModifierList ml) const; + ~RuleNative(); + + void addCondition(const Condition * c); + void addAction(const Action * a); + RuleNative * clone() const; + + ModifierList modifiers; + + // There can be more than one of certain conditions (lists, socketGroups). + // We assume that there is at most one of others (interval, bool). + std::multimap conditions; + + // Only the last action of each type is preserved. + std::map actions; + + // True if the rule does not match anything. + // In this case we do not guarantee that the list of conditions will be anything sensible. + bool useless; + + int guid; +}; + +typedef std::vector FilterNative; +std::ostream & print(std::ostream & os, PrintStyle ps, const FilterNative & f); + +class Compiler { +public: + Compiler(Logger & l, bool wp, std::ostream & ps) : log(l), writePartial(wp), partialStream(ps) {}; + void Compile(const Filter & inFilter, FilterNative & outFilter); + +private: + Logger & log; + bool writePartial; + std::ostream & partialStream; +}; + +} + +#endif \ No newline at end of file diff --git a/src/Context.cpp b/src/Context.cpp new file mode 100644 index 0000000..b134627 --- /dev/null +++ b/src/Context.cpp @@ -0,0 +1,134 @@ +extern const int IFPP_VERSION_MAJOR; +extern const int IFPP_VERSION_MINOR; +extern const int IFPP_VERSION_PATCH; + +#include "Context.h" + +#include + +#include "location.hh" + +namespace ifpp { + +void Context::reset() { + for (const auto & stm : filter) delete stm; + filter.clear(); + + countIns = 0; + countDef = 0; + countRule = 0; + + varNumber.clear(); + varColor.clear(); + varFile.clear(); + varList.clear(); + + for (const auto & s : varStyle) { + for (const auto & a : s.second) { + delete a; + } + } + varStyle.clear(); +} + +bool Context::versionCheck(int vMajor, int vMinor, int vPatch) const { + return vMajor == IFPP_VERSION_MAJOR + && vMinor == IFPP_VERSION_MINOR + && vPatch == IFPP_VERSION_PATCH; +} + +void Context::defineVariable(const std::string & name, VariableType type, int value) { + if (type != VAR_NUMBER) throw InternalError("Defining a variable with the wrong type! Expected VAR_NUMBER.", __FILE__, __LINE__); + varNumber[name] = value; +} + +void Context::defineVariable(const std::string & name, VariableType type, const Color & value) { + if (type != VAR_COLOR) throw InternalError("Defining a variable with the wrong type! Expected VAR_COLOR.", __FILE__, __LINE__); + varColor[name] = value; +} + +void Context::defineVariable(const std::string & name, VariableType type, const std::string & value) { + if (type != VAR_FILE) throw InternalError("Defining a variable with the wrong type! Expected VAR_FILE.", __FILE__, __LINE__); + varFile[name] = value; +} + +void Context::defineVariable(const std::string & name, VariableType type, const NameList & value) { + if (type != VAR_LIST) throw InternalError("Defining a variable with the wrong type! Expected VAR_LIST.", __FILE__, __LINE__); + varList[name] = value; +} + +void Context::defineVariable(const std::string & name, VariableType type, const Style & value) { + if (type != VAR_STYLE) throw InternalError("Defining a variable with the wrong type! Expected VAR_STYLE.", __FILE__, __LINE__); + varStyle[name] = value; +} + +void Context::undefineVariable(const std::string & name) { + switch (getVarType(name)) { + case VAR_NUMBER: varNumber.erase(name); break; + case VAR_COLOR: varColor.erase(name); break; + case VAR_FILE: varFile.erase(name); break; + case VAR_LIST: varList.erase(name); break; + case VAR_STYLE: + for (const auto & a : varStyle[name]) delete a; + varStyle.erase(name); + break; + case VAR_UNDEFINED: break; + default: throw UnhandledCase("Variable type", __FILE__, __LINE__); + } +} + +VariableType Context::getVarType(const std::string & name) const { + if (varNumber.count(name) > 0) return VAR_NUMBER; + if (varColor.count(name) > 0) return VAR_COLOR; + if (varFile.count(name) > 0) return VAR_FILE; + if (varList.count(name) > 0) return VAR_LIST; + if (varStyle.count(name) > 0) return VAR_STYLE; + return VAR_UNDEFINED; +} + +// std::map::at throws std::out_of_range if value was not found. +// We should not rely on this, always check before fetching. +int Context::getVarValueNumber(const std::string & name) const { + return varNumber.at(name); +} + +const Color & Context::getVarValueColor(const std::string & name) const { + return varColor.at(name); +} + +const std::string & Context::getVarValueFile(const std::string & name) const { + return varFile.at(name); +} + +const NameList & Context::getVarValueList(const std::string & name) const { + return varList.at(name); +} + +const Style & Context::getVarValueStyle(const std::string & name) const { + return varStyle.at(name); +} + +void Context::addStatement(Statement * stm) { + switch (stm->stmType) { + case STM_INSTRUCTION: ++countIns; break; + case STM_DEFINITION: ++countDef; break; + case STM_RULE: ++countRule; break; + default: throw UnhandledCase("Statement type", __FILE__, __LINE__); + } + filter.push_back(stm); +} + +std::ostream & Context::warningAt(const yy::location & l) { + return log.warning() << "Line " << l << ": "; +} + +std::ostream & Context::errorAt(const yy::location & l) { + return log.error() << "Line " << l << ": "; +} + +std::ostream & Context::criticalAt(const yy::location & l) { + return log.critical() << "Line " << l << ": "; +} + + +} diff --git a/src/Context.h b/src/Context.h new file mode 100644 index 0000000..013c95b --- /dev/null +++ b/src/Context.h @@ -0,0 +1,61 @@ +#ifndef IFPP_CONTEXT_H +#define IFPP_CONTEXT_H + +#include "Types.h" +#include "Logger.h" + +namespace yy { + class location; +} + +namespace ifpp { + +class Context { +public: + Context(const std::string & f, FilterIFPP & F, Logger & l) : + file(f), filter(F), countIns(0), countDef(0), countRule(0), log(l), + varNumber(), varColor(), varFile(), varList(), varStyle() {}; + + void reset(); + + bool versionCheck(int vMajor, int vMinor, int vPatch) const; + + void defineVariable(const std::string & name, ifpp::VariableType type, int value); + void defineVariable(const std::string & name, ifpp::VariableType type, const ifpp::Color & value); + void defineVariable(const std::string & name, ifpp::VariableType type, const std::string & value); + void defineVariable(const std::string & name, ifpp::VariableType type, const ifpp::NameList & value); + void defineVariable(const std::string & name, ifpp::VariableType type, const ifpp::Style & value); + + void undefineVariable(const std::string & name); + + ifpp::VariableType getVarType(const std::string & name) const; + + int getVarValueNumber(const std::string & name) const; + const ifpp::Color & getVarValueColor(const std::string & name) const; + const std::string & getVarValueFile(const std::string & name) const; + const ifpp::NameList & getVarValueList(const std::string & name) const; + const ifpp::Style & getVarValueStyle(const std::string & name) const; + + void addStatement(Statement * stm); + + std::ostream & warningAt(const yy::location & l); + std::ostream & errorAt(const yy::location & l); + std::ostream & criticalAt(const yy::location & l); + + std::string file; + FilterIFPP & filter; + int countIns, countDef, countRule; + +private: + Logger & log; + + std::map varNumber; + std::map varColor; + std::map varFile; + std::map varList; + std::map varStyle; +}; + +} + +#endif \ No newline at end of file diff --git a/src/Lexer.l b/src/Lexer.l new file mode 100644 index 0000000..a45a66e --- /dev/null +++ b/src/Lexer.l @@ -0,0 +1,218 @@ +%{ + +#include +#include +#include +#include + +#include "Parser.h" + +#include "Types.h" +#include "Context.h" + +#define YY_DECL yy::Parser::symbol_type yylex(ifpp::Context & ctx) +YY_DECL; + +static yy::location loc; +// Code run each time a pattern is matched. +#define YY_USER_ACTION { loc.step(); loc.columns(yyleng); } + +// Work around an incompatibility in flex (at least versions +// 2.5.31 through 2.5.33): it generates code that does +// not conform to C89. See Debian bug 333231 +// . +# undef yywrap +# define yywrap() 1 + +%} + +%option noyywrap nounput batch noinput debug + +%x comment +%x nameList + +blank [ \t\r] + +idPrefix "$" +id [[:alpha:][:digit:]_] + +hex [0-9a-fA-F] +socket [rgbwRGBW] + +comStart "//"|"#" +garbage [^ \t\r\n.><={}/#] + +%% + +{blank}*\n loc.lines(); return yy::Parser::make_NEWLINE(loc); +{blank}+ + +{blank}*{comStart}[^\n]* BEGIN(INITIAL); +{blank}*{comStart}[^\n]*\n { + // For error reporting, the \n should still be a part of the last line. + yy::location l = loc; + loc.lines(); + BEGIN(INITIAL); + return yy::Parser::make_NEWLINE(l); +} + +"/*" BEGIN(comment); +[^*\n]* +"*"+[^*/\n]* +\n loc.lines(); +"*"+"/" BEGIN(INITIAL); + + +"{" return yy::Parser::make_CHR_LEFTBRACKET(loc); +"}" return yy::Parser::make_CHR_RIGHTBRACKET(loc); +"." return yy::Parser::make_CHR_PERIOD(loc); +".." return yy::Parser::make_CHR_DOTDOT(loc); +":" return yy::Parser::make_CHR_COLON(loc); + +"<" return yy::Parser::make_OP_LT(ifpp::OP_LT, loc); +"<=" return yy::Parser::make_OP_LE(ifpp::OP_LE, loc); +"=" return yy::Parser::make_OP_EQ(ifpp::OP_EQ, loc); +">=" return yy::Parser::make_OP_GE(ifpp::OP_GE, loc); +">" return yy::Parser::make_OP_GT(ifpp::OP_GT, loc); + +Rule return yy::Parser::make_KW_RULE(loc); +Default return yy::Parser::make_KW_DEFAULT(loc); + +Override return yy::Parser::make_KW_OVERRIDE(ifpp::MOD_OVERRIDE, loc); + +Define return yy::Parser::make_KW_DEFINE(loc); +Redefine return yy::Parser::make_KW_REDEFINE(loc); + +Version return yy::Parser::make_KW_VERSION(loc); + +ItemLevel return yy::Parser::make_CON_ITEMLEVEL("ItemLevel", loc); +DropLevel return yy::Parser::make_CON_DROPLEVEL("DropLevel", loc); +Quality return yy::Parser::make_CON_QUALITY("Quality", loc); +Sockets return yy::Parser::make_CON_SOCKETS("Sockets", loc); +LinkedSockets return yy::Parser::make_CON_LINKEDSOCKETS("LinkedSockets", loc); +Height return yy::Parser::make_CON_HEIGHT("Height", loc); +Width return yy::Parser::make_CON_WIDTH("Width", loc); +StackSize return yy::Parser::make_CON_STACKSIZE("StackSize", loc); +GemLevel return yy::Parser::make_CON_GEMLEVEL("GemLevel", loc); +MapTier return yy::Parser::make_CON_MAPTIER("MapTier", loc); + +Rarity return yy::Parser::make_CON_RARITY("Rarity", loc); + +Identified return yy::Parser::make_CON_IDENTIFIED("Identified", loc); +Corrupted return yy::Parser::make_CON_CORRUPTED("Corrupted", loc); +ElderItem return yy::Parser::make_CON_ELDERITEM("ElderItem", loc); +ShaperItem return yy::Parser::make_CON_SHAPERITEM("ShaperItem", loc); +ShapedMap return yy::Parser::make_CON_SHAPEDMAP("ShapedMap", loc); + +Class BEGIN(nameList); return yy::Parser::make_CON_CLASS("Class", loc); +BaseType BEGIN(nameList); return yy::Parser::make_CON_BASETYPE("BaseType", loc); +HasExplicitMod BEGIN(nameList); return yy::Parser::make_CON_EXPLICIT("HasExplicitMod", loc); + +SocketGroup return yy::Parser::make_CON_SOCKETGROUP("SocketGroup", loc); + +SetFontSize return yy::Parser::make_AC_FONTSIZE("SetFontSize", loc); +SetTextSize return yy::Parser::make_AC_FONTSIZE("SetFontSize", loc); + +SetBorderColor return yy::Parser::make_AC_BORDERCOLOR("SetBorderColor", loc); +SetTextColor return yy::Parser::make_AC_TEXTCOLOR("SetTextColor", loc); +SetFontColor return yy::Parser::make_AC_TEXTCOLOR("SetTextColor", loc); +SetBackgroundColor return yy::Parser::make_AC_BGCOLOR("SetBackgroundColor", loc); + +PlayAlertSound return yy::Parser::make_AC_SOUND("PlayAlertSound", loc); +PlayAlertSoundPositional return yy::Parser::make_AC_SOUNDPOSITIONAL("PlayAlertSoundPositional", loc); + +CustomAlertSound return yy::Parser::make_AC_CUSTOMSOUND("CustomAlertSound", loc); +MinimapIcon return yy::Parser::make_AC_MINIMAPICON("MinimapIcon", loc); +PlayEffect return yy::Parser::make_AC_PLAYEFFECT("PlayEffect", loc); + +DisableDropSound return yy::Parser::make_AC_DISABLESOUND("DisableDropSound", loc); +Hidden return yy::Parser::make_AC_HIDDEN("Hidden", loc); + +UseStyle return yy::Parser::make_AC_USESTYLE("UseStyle", loc); + +Number return yy::Parser::make_TYPE_NUMBER(ifpp::VAR_NUMBER, loc); +Color return yy::Parser::make_TYPE_COLOR(ifpp::VAR_COLOR, loc); +File return yy::Parser::make_TYPE_FILE(ifpp::VAR_FILE, loc); +List BEGIN(nameList); return yy::Parser::make_TYPE_LIST(ifpp::VAR_LIST, loc); +Style return yy::Parser::make_TYPE_STYLE(ifpp::VAR_STYLE, loc); + +true return yy::Parser::make_CONST_BOOL(true, loc); +false return yy::Parser::make_CONST_BOOL(false, loc); + +Normal return yy::Parser::make_CONST_RARITY(ifpp::Normal, loc); +Magic return yy::Parser::make_CONST_RARITY(ifpp::Magic, loc); +Rare return yy::Parser::make_CONST_RARITY(ifpp::Rare, loc); +Unique return yy::Parser::make_CONST_RARITY(ifpp::Unique, loc); + +ItemFilterAlert10 | +ItemFilterAlert11 | +ItemFilterAlert12 | +ItemFilterAlert13 | +ItemFilterAlert14 | +ItemFilterAlert15 | +ItemFilterAlert16 | +ShAlchemy | +ShBlessed | +ShChaos | +ShDivine | +ShExalted | +ShFusing | +ShGeneral | +ShMirror | +ShRegal | +ShVaal return yy::Parser::make_CONST_SOUND(yytext, loc); + +Red return yy::Parser::make_CONST_COLOR("Red", loc); +Green return yy::Parser::make_CONST_COLOR("Green", loc); +Blue return yy::Parser::make_CONST_COLOR("Blue", loc); +Brown return yy::Parser::make_CONST_COLOR("Brown", loc); +White return yy::Parser::make_CONST_COLOR("White", loc); +Yellow return yy::Parser::make_CONST_COLOR("Yellow", loc); + +Circle return yy::Parser::make_CONST_SHAPE("Circle", loc); +Diamond return yy::Parser::make_CONST_SHAPE("Diamond", loc); +Hexagon return yy::Parser::make_CONST_SHAPE("Hexagon", loc); +Square return yy::Parser::make_CONST_SHAPE("Square", loc); +Star return yy::Parser::make_CONST_SHAPE("Star", loc); +Triangle return yy::Parser::make_CONST_SHAPE("Triangle", loc); + +Temp return yy::Parser::make_CONST_TEMP("Temp", loc); + +[[:digit:]]+ { + errno = 0; + long n = strtol(yytext, NULL, 10); + if (errno == ERANGE || n > INT_MAX) { + ctx.errorAt(loc) << "Integer constant too large! Using a really big number, this is probably not what you want." << std::endl; + n = INT_MAX; + } + return yy::Parser::make_NUMBER(n, loc); +} + +{idPrefix}{id}+ return yy::Parser::make_VARIABLE(yytext, loc); +x{hex}+ return yy::Parser::make_HEX(yytext + 1, loc); +{socket}+ return yy::Parser::make_SOCKETGROUP(std::string(yytext), loc); +\"[^"\r\n]*\" return yy::Parser::make_FILENAME(yytext, loc); + +{blank}+ +\n loc.lines(); BEGIN(INITIAL); return yy::Parser::make_NEWLINE(loc); +{idPrefix}{id}+ return yy::Parser::make_VARIABLE(yytext, loc); + +\"[^"\r\n]*\" { + std::string s(yytext + 1, yyleng - 2); + return yy::Parser::make_NAME(s, loc); +} + +[^ \r\n"]+ return yy::Parser::make_NAME(yytext, loc); + +{garbage}* ctx.criticalAt(loc) << "String \"" << yytext << "\" not recognized!" << std::endl; + +<> { + static bool first = true; + if (first) { + first = false; + return yy::Parser::make_NEWLINE(loc); + } + return yy::Parser::make_END(loc); +} + +%% diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..d12c65a --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1 @@ +#include "Logger.h" diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 0000000..79fb26c --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,35 @@ +#ifndef IFPP_LOGGER_H +#define IFPP_LOGGER_H + +#include + +namespace ifpp { + +class Logger { +public: + Logger(std::ostream & out) : + numWarnings(0), numErrors(0), numCritical(0), log(out) {} + + std::ostream & message() { return log; } + std::ostream & messageAppend() { return log; } + + std::ostream & warning() { ++numWarnings; return log << "Warning: ";} + std::ostream & warningAppend() { return log; } + + std::ostream & error() { ++numErrors; return log << "Error: "; } + std::ostream & errorAppend() { return log; } + + std::ostream & critical() { ++numCritical; return log << "CRITICAL ERROR: "; } + std::ostream & criticalAppend() { return log; } + + int numWarnings; + int numErrors; + int numCritical; + +private: + std::ostream & log; +}; + +} + +#endif \ No newline at end of file diff --git a/src/Parser.y b/src/Parser.y new file mode 100644 index 0000000..bce4ea9 --- /dev/null +++ b/src/Parser.y @@ -0,0 +1,690 @@ +%skeleton "lalr1.cc" +%require "3.0.4" + +%define parser_class_name { Parser } +%define api.token.constructor +%define api.value.type variant +%define parse.assert +%define parse.trace +%define parse.error verbose + +%code requires { + #include + #include + #include + + #include "Types.h" + #include "Context.h" +} + +%param { ifpp::Context & ctx } + +%locations + +%initial-action { + // Initialize the initial location. + @$.begin.filename = @$.end.filename = &ctx.file; +}; + +%code { + #define YY_DECL yy::Parser::symbol_type yylex(ifpp::Context & ctx) + YY_DECL; + + void yy::Parser::error(const location_type & l, const std::string & m) { + ctx.criticalAt(l) << "Parser returned an error: " << m << std::endl; + } + + static bool clampValue(ifpp::Context & ctx, const yy::location & l, const std::string & what, int & value, int min, int max) { + int replace = value; + if (value < min) replace = min; + if (value > max) replace = max; + + if (replace != value) { + ctx.warningAt(l) << "Value " << value << " is outside of allowed range for " << what << ": [" << min << ", " << max << "]. " + << " The value was replaced by " << replace << "." << std::endl; + value = replace; + return false; + } + return true; + } + + static bool clampInterval(ifpp::Context & ctx, const yy::location & l, const std::string & what, int from, int to, int min, int max) { + if (from > to) { + ctx.warningAt(l) << "The range of condition " << what << " is empty. " + << "The condition will not match any items." << std::endl; + return false; + } + if (from > max || to < min) { + ctx.warningAt(l) << "Condition " << what << " does not match any possible values from the interval [" << min << ", " << max << "]. " + << "The condition will not match any items." << std::endl; + return false; + } + if (from <= min && to >= max) { + ctx.warningAt(l) << "Condition " << what << " matches all possible values from the interval [" << min << ", " << max << "]. " + << "The condition will match all items." << std::endl; + return false; + } + return true; + } + + static bool checkVarUse(ifpp::Context & ctx, const yy::location & l, const std::string & name, ifpp::VariableType type) { + ifpp::VariableType oldType = ctx.getVarType(name); + if (oldType == ifpp::VAR_UNDEFINED) { + if (type == ifpp::VAR_LIST) { + ctx.errorAt(l) << "Variable " << name << " has not been defined. " + << "It will be ignored in this list." << std::endl; + } else { + ctx.errorAt(l) << "Variable " << name << " has not been defined. " + << "This command will be ignored." << std::endl; + } + return false; + } else if (oldType != type) { + if (type == ifpp::VAR_LIST) { + ctx.errorAt(l) << "Variable " << name << " is of type " << oldType << ", but this command requires " << type << ". " + << "The variable will be ignored in this list." << std::endl; + } else { + ctx.errorAt(l) << "Variable " << name << " is of type " << oldType << ", but this command requires " << type << ". " + << "This command will be ignored." << std::endl; + } + return false; + } + return true; + } + + template + static ifpp::DefinitionBase * magicDefinition(ifpp::Context & ctx, const yy::location & l, + const std::string & name, ifpp::VariableType type, const T & value, bool redefine = false) { + + ifpp::VariableType oldType = ctx.getVarType(name); + if (redefine) { + if (oldType == ifpp::VAR_UNDEFINED) { + ctx.warningAt(l) << "Variable " << name << " has not been defined yet. " + << "It will be defined now. Use the Define command instead to avoid this warning." << std::endl; + } else if (oldType != type) { + ctx.errorAt(l) << "Variable " << name << " has already been defined as type " << oldType << "! " + << "The variable will be replaced by the new value of type " << type << "." << std::endl; + ctx.undefineVariable(name); + } + } else { + if (oldType == type) { + ctx.warningAt(l) << "Variable " << name << " has already been defined. " + << "It will be replaced by the new value. Use the Redefine command instead to avoid this warning." << std::endl; + } else if (oldType != ifpp::VAR_UNDEFINED) { + ctx.errorAt(l) << "Variable " << name << " has already been defined as type " << oldType << "! " + << "The variable will be replaced by the new value of type " << type << "." << std::endl; + ctx.undefineVariable(name); + } + } + + ctx.defineVariable(name, type, value); + return new ifpp::Definition(name, type, value); + } + + static ifpp::ConditionInterval * magicInterval(ifpp::Context & ctx, const yy::location & l, + const std::string & what, int from, int to) { + + clampInterval(ctx, l, what, from, to, ifpp::getLimit(what, ifpp::MIN), ifpp::getLimit(what, ifpp::MAX)); + return new ifpp::ConditionInterval(what, from, to); + } + + static ifpp::ConditionInterval * magicInterval(ifpp::Context & ctx, const yy::location & l, + const std::string & what, ifpp::Operator op, int value) { + + int from = INT_MIN, to = INT_MAX; + switch(op) { + case ifpp::OP_LT: to = value - 1; break; + case ifpp::OP_LE: to = value; break; + case ifpp::OP_EQ: from = to = value; break; + case ifpp::OP_GE: from = value; break; + case ifpp::OP_GT: from = value + 1; break; + default: throw ifpp::UnhandledCase("Operator", __FILE__, __LINE__); + } + return magicInterval(ctx, l, what, from, to); + } + + template static void listNew(std::vector & l) { + l.clear(); + } + + template static void listNew(std::vector & l, E * e) { + l.clear(); + if (e) l.push_back(e); + } + + template static void listNew(std::vector & l, const E & e) { + l.clear(); + l.push_back(e); + } + + template static void listCopy(std::vector & l1, std::vector & l2) { + l1.swap(l2); + } + + template static void listAppend(std::vector & l1, std::vector & l2, E * e) { + l1.swap(l2); + if (e) l1.push_back(e); + } + + template static void listAppend(std::vector & l1, std::vector & l2, const E & e) { + l1.swap(l2); + l1.push_back(e); + } + + template static void listMerge(std::vector & l1, std::vector & l2, const std::vector & l3) { + l1.swap(l2); + l1.insert(l1.end(), l3.begin(), l3.end()); + } +} + +%token + NEWLINE "end of line" + + CHR_LEFTBRACKET "{" + CHR_RIGHTBRACKET "}" + CHR_PERIOD "." + CHR_DOTDOT ".." + CHR_COLON ":" + + KW_RULE "Rule" + KW_DEFAULT "Default" + + KW_DEFINE "Define" + KW_REDEFINE "Redefine" + + KW_VERSION "Version" +; + +%token + TYPE_NUMBER "Number" + TYPE_COLOR "Color" + TYPE_FILE "File" + TYPE_LIST "List" + TYPE_STYLE "Style" +; + +%token + OP_LT "<" + OP_LE "<=" + OP_EQ "=" + OP_GE ">=" + OP_GT ">" +; + +%token SOCKETGROUP "socket group" + +%token KW_OVERRIDE "Override" + +%token CONST_BOOL "boolean value" + +%token + CON_ITEMLEVEL "ItemLevel" + CON_DROPLEVEL "DropLevel" + CON_QUALITY "Quality" + CON_SOCKETS "Sockets" + CON_LINKEDSOCKETS "LinkedSockets" + CON_HEIGHT "Height" + CON_WIDTH "Width" + CON_STACKSIZE "StackSize" + CON_GEMLEVEL "GemLevel" + CON_MAPTIER "MapTier" + + CON_RARITY "Rarity" + + CON_CLASS "Class" + CON_BASETYPE "BaseType" + CON_EXPLICIT "HasExplicitMod" + + CON_IDENTIFIED "Identified" + CON_CORRUPTED "Corrupted" + CON_ELDERITEM "ElderItem" + CON_SHAPERITEM "ShaperItem" + CON_SHAPEDMAP "ShapedMap" + + CON_SOCKETGROUP "SocketGroup" + + AC_FONTSIZE "SetFontSize" + + AC_BORDERCOLOR "SetBorderColor" + AC_TEXTCOLOR "SetTextColor" + AC_BGCOLOR "SetBackgroundColor" + + AC_SOUND "PlayAlertSound" + AC_SOUNDPOSITIONAL "PlayAlertSoundPositional" + + AC_DISABLESOUND "DisableDropSound" + AC_HIDDEN "Hidden" + + AC_CUSTOMSOUND "CustomAlertSound" + AC_MINIMAPICON "MinimapIcon" + AC_PLAYEFFECT "PlayEffect" + + AC_USESTYLE "UseStyle" + + CONST_SOUND "sound" + CONST_COLOR "color" + CONST_SHAPE "shape" + CONST_TEMP "Temp" + + VARIABLE "variable" + HEX "hex number" + FILENAME "file name" + + NAME "name" +; + +%token + NUMBER "number" + CONST_RARITY "rarity" +; + +%token END 0 "end of file" + + +%type instruction +%type definition + +%type + ruleBody + conditions + actions + actionsNotEmpty + rules + rulesNotEmpty +; + +%type condition +%type action +%type defaultStyle +%type rule + +%type + style + actionStyle + exprStyle + varStyle +; + +%type operator + +%type + modifiers + modifier +; + +%type + conditionNumeric + conditionRarity + conditionBool + conditionNameList + conditionSocketGroup + + actionNumber + actionColor + actionBool + actionSound + + soundId + + exprFile + varFile +; + +%type + soundVolume + exprNumber + exprReallyNumber + varNumber +; + +%type + exprColor + varColor +; + +%type + exprList + varList +; + +%type + defineOrRedefine + exprBool +; + +%printer { ifpp::print(yyoutput, ifpp::PRINT_IFPP, $$); } + + + + + + + + +; + +%printer { ifpp::operator<<(yyoutput, $$); } + +; + +%printer { yyoutput << $$; } <*> + +%% + +%start statements; + +statements: +%empty { } +| statements instruction { ctx.addStatement($2); } +| statements definition { ctx.addStatement($2); } +| statements rule { ctx.addStatement($2); } +| statements NEWLINE { } +| statements error NEWLINE { } + +instruction: +KW_VERSION NUMBER[vMajor] CHR_PERIOD NUMBER[vMinor] CHR_PERIOD NUMBER[vBugfix] NEWLINE { + if (ctx.versionCheck($vMajor, $vMinor, $vBugfix)) { + $$ = new ifpp::InstructionVersion($vMajor, $vMinor, $vBugfix); + } else { + // If it is impossible to continue, the context should abort parsing gracefully. + YYABORT; + } +} + +definition: + defineOrRedefine VARIABLE TYPE_NUMBER exprNumber NEWLINE + { $$ = magicDefinition(ctx, @$, $2, $3, $4, $1); } +| defineOrRedefine VARIABLE TYPE_COLOR exprColor NEWLINE + { $$ = magicDefinition(ctx, @$, $2, $3, $4, $1); } +| defineOrRedefine VARIABLE TYPE_FILE exprFile NEWLINE + { $$ = magicDefinition(ctx, @$, $2, $3, $4, $1); } +| defineOrRedefine VARIABLE TYPE_LIST exprList NEWLINE + { $$ = magicDefinition(ctx, @$, $2, $3, $4, $1); } +| defineOrRedefine VARIABLE TYPE_STYLE newlines CHR_LEFTBRACKET NEWLINE style CHR_RIGHTBRACKET NEWLINE + { $$ = magicDefinition(ctx, @$, $2, $3, $style, $1); } + +defineOrRedefine: + KW_DEFINE { $$ = false; } +| KW_REDEFINE { $$ = true; } + +rule: +KW_RULE modifiers newlines CHR_LEFTBRACKET NEWLINE ruleBody CHR_RIGHTBRACKET NEWLINE { + $$ = new ifpp::RuleIFPP($modifiers, $ruleBody); +} + +ruleBody: +conditions actions rules defaultStyle { + $$.insert($$.end(), $1.begin(), $1.end()); + $$.insert($$.end(), $2.begin(), $2.end()); + $$.insert($$.end(), $3.begin(), $3.end()); + if ($4) $$.push_back($4); +} + +conditions: +%empty {} +| conditions condition { listAppend($$, $1, $2); } +| conditions NEWLINE { listCopy($$, $1); } +| conditions error NEWLINE { listCopy($$, $1); } + +actions: +%empty { /* To fix ambiguity when a rule only contains newlines. */ } +| actionsNotEmpty { listCopy($$, $1); } + +actionsNotEmpty: +action { listAppend($$, $$, $1); } +| actionStyle { listMerge($$, $$, $1); } +| actionsNotEmpty action { listAppend($$, $1, $2); } +| actionsNotEmpty actionStyle { listMerge($$, $1, $2); } +| actionsNotEmpty NEWLINE { listCopy($$, $1); } +| actionsNotEmpty error NEWLINE { listCopy($$, $1); } + +rules: +%empty { /* To fix ambiguity when a rule only contains newlines. */ } +| rulesNotEmpty { $$ = $1; } + +rulesNotEmpty: +rule { listAppend($$, $$, $1); } +| rulesNotEmpty rule { listAppend($$, $1, $2); } +| rulesNotEmpty NEWLINE { listCopy($$, $1); } +| rulesNotEmpty error NEWLINE { listCopy($$, $1); } + +defaultStyle: +%empty { $$ = NULL; } +| KW_DEFAULT newlines CHR_LEFTBRACKET NEWLINE style CHR_RIGHTBRACKET newlines { $$ = new ifpp::DefaultStyle($style); } + +newlines: +%empty {} +| newlines NEWLINE {} + + + +condition: + conditionNumeric[what] operator[op] exprNumber[value] NEWLINE + { $$ = magicInterval(ctx, @$, $what, $op, $value); } +| conditionNumeric[what] exprNumber[value] NEWLINE + { $$ = magicInterval(ctx, @$, $what, $value, $value); } +| conditionNumeric[what] exprNumber[from] CHR_DOTDOT exprNumber[to] NEWLINE + { $$ = magicInterval(ctx, @$, $what, $from, $to); } +| conditionRarity[what] operator[op] CONST_RARITY[value] NEWLINE + { $$ = magicInterval(ctx, @$, $what, $op, $value); } +| conditionRarity[what] CONST_RARITY[value] NEWLINE + { $$ = magicInterval(ctx, @$, $what, $value, $value); } +| conditionRarity[what] CONST_RARITY[from] CHR_DOTDOT CONST_RARITY[to] NEWLINE + { $$ = magicInterval(ctx, @$, $what, $from, $to); } +| conditionBool[what] exprBool[value] NEWLINE + { $$ = new ifpp::ConditionBool($what, $value); } +| conditionNameList[what] exprList[list] NEWLINE + { $$ = new ifpp::ConditionNameList($what, $list); } +| conditionSocketGroup[what] SOCKETGROUP[value] NEWLINE + { $$ = new ifpp::ConditionSocketGroup($what, $value); } + +conditionNumeric: + CON_ITEMLEVEL { $$ = $1; } +| CON_DROPLEVEL { $$ = $1; } +| CON_QUALITY { $$ = $1; } +| CON_SOCKETS { $$ = $1; } +| CON_LINKEDSOCKETS { $$ = $1; } +| CON_HEIGHT { $$ = $1; } +| CON_WIDTH { $$ = $1; } +| CON_STACKSIZE { $$ = $1; } +| CON_GEMLEVEL { $$ = $1; } +| CON_MAPTIER { $$ = $1; } + +conditionRarity: +CON_RARITY { $$ = $1; } + +conditionBool: + CON_IDENTIFIED { $$ = $1; } +| CON_CORRUPTED { $$ = $1; } +| CON_ELDERITEM { $$ = $1; } +| CON_SHAPERITEM { $$ = $1; } +| CON_SHAPEDMAP { $$ = $1; } + +conditionNameList: + CON_CLASS { $$ = $1; } +| CON_BASETYPE { $$ = $1; } +| CON_EXPLICIT { $$ = $1; } + +conditionSocketGroup: +CON_SOCKETGROUP { $$ = $1; } + + + +action: + modifiers actionNumber[what] exprNumber[value] NEWLINE { + clampValue(ctx, @$, $what, $value, ifpp::getLimit($what, ifpp::MIN), ifpp::getLimit($what, ifpp::MAX)); + $$ = new ifpp::ActionNumber($modifiers, $what, $value); +} +| modifiers actionColor[what] exprColor[value] NEWLINE + { $$ = new ifpp::ActionColor($modifiers, $what, $value); } +| modifiers actionBool[what] exprBool[value] NEWLINE + { $$ = new ifpp::ActionBool($modifiers, $what, $value); } +| modifiers actionSound[what] soundId soundVolume NEWLINE + { $$ = new ifpp::ActionSound($modifiers, $what, $soundId, $soundVolume); } +| modifiers AC_CUSTOMSOUND[what] exprFile[file] NEWLINE + { $$ = new ifpp::ActionFile($modifiers, $what, $file); } +| modifiers AC_MINIMAPICON[what] exprNumber[size] CONST_COLOR[color] CONST_SHAPE[shape] NEWLINE { + clampValue(ctx, @$, $what, $size, ifpp::getLimit($what, ifpp::MIN), ifpp::getLimit($what, ifpp::MAX)); + $$ = new ifpp::ActionMapIcon($modifiers, $what, $size, $color, $shape); +} +| modifiers AC_PLAYEFFECT[what] CONST_COLOR[color] NEWLINE + { $$ = new ifpp::ActionEffect($modifiers, $what, $color, ""); } +| modifiers AC_PLAYEFFECT[what] CONST_COLOR[color] CONST_TEMP[temp] NEWLINE + { $$ = new ifpp::ActionEffect($modifiers, $what, $color, $temp); } + +actionNumber: +AC_FONTSIZE { $$ = $1; } + +actionColor: + AC_TEXTCOLOR { $$ = $1; } +| AC_BGCOLOR { $$ = $1; } +| AC_BORDERCOLOR { $$ = $1; } + +actionBool: + AC_HIDDEN { $$ = $1; } +| AC_DISABLESOUND { $$ = $1; } + +actionSound: + AC_SOUND { $$ = $1; } +| AC_SOUNDPOSITIONAL { $$ = $1; } + +actionStyle: +modifiers AC_USESTYLE exprStyle[style] NEWLINE { + listNew($$); + for (const auto & a : $style) { + ifpp::Action * aa = static_cast(a->clone()); + aa->modifiers |= $modifiers; + $$.push_back(aa); + } +} + + + +modifiers: +%empty { $$ = 0; } +| modifiers modifier { $$ |= $1; } + +modifier: +KW_OVERRIDE { $$ = $1; } + + + +style: +actions { + for (auto & a : $1) { + $$.push_back(static_cast(a)); + } +} + +operator: + OP_LT { $$ = $1; } +| OP_LE { $$ = $1; } +| OP_EQ { $$ = $1; } +| OP_GE { $$ = $1; } +| OP_GT { $$ = $1; } + +soundId: +exprNumber { + std::stringstream ss; + ss << $1; + $$ = ss.str(); +} +| CONST_SOUND { $$ = $1; } + +soundVolume: +%empty { $$ = ifpp::getLimit("Volume", ifpp::DEFAULT); } +| exprNumber { + clampValue(ctx, @1, "Sound volume", $1, ifpp::getLimit("Volume", ifpp::MIN), ifpp::getLimit("Volume", ifpp::MAX)); + $$ = $1; +} + + + +exprNumber: +varNumber { $$ = $1; } +| exprReallyNumber { $$ = $1; } + +exprReallyNumber: +NUMBER { $$ = $1; } + +exprBool: +%empty { $$ = true; } +| CONST_BOOL { $$ = $1; } + +exprColor: +HEX { $$ = ifpp::Color($1); } +| varColor { $$ = $1; } +| varColor CHR_COLON exprNumber { $$ = $1; $$.a = $3; } +| exprNumber exprNumber exprNumber { + clampValue(ctx, @1, "Color value", $1, ifpp::getLimit("Color", ifpp::MIN), ifpp::getLimit("Color", ifpp::MAX)); + clampValue(ctx, @2, "Color value", $2, ifpp::getLimit("Color", ifpp::MIN), ifpp::getLimit("Color", ifpp::MAX)); + clampValue(ctx, @3, "Color value", $3, ifpp::getLimit("Color", ifpp::MIN), ifpp::getLimit("Color", ifpp::MAX)); + $$ = ifpp::Color($1, $2, $3); +} +| exprNumber exprNumber exprNumber exprNumber { + clampValue(ctx, @1, "Color value", $1, ifpp::getLimit("Color", ifpp::MIN), ifpp::getLimit("Color", ifpp::MAX)); + clampValue(ctx, @2, "Color value", $2, ifpp::getLimit("Color", ifpp::MIN), ifpp::getLimit("Color", ifpp::MAX)); + clampValue(ctx, @3, "Color value", $3, ifpp::getLimit("Color", ifpp::MIN), ifpp::getLimit("Color", ifpp::MAX)); + clampValue(ctx, @4, "Color value", $4, ifpp::getLimit("Color", ifpp::MIN), ifpp::getLimit("Color", ifpp::MAX)); + $$ = ifpp::Color($1, $2, $3, $4); +} + +exprFile: +varFile { $$ = $1; } +| FILENAME { $$ = $1; } + +exprList: +%empty { listNew($$); } +| exprList NAME { listAppend($$, $1, $2); } +| exprList varList[var] { listMerge($$, $1, $2); } + +exprStyle: +varStyle { $$ = $1; } + + + +varNumber: +VARIABLE { + if (checkVarUse(ctx, @1, $1, ifpp::VAR_NUMBER)) { + $$ = ctx.getVarValueNumber($1); + } else { + YYERROR; + } +} + +varColor: +VARIABLE { + if (checkVarUse(ctx, @1, $1, ifpp::VAR_COLOR)) { + $$ = ctx.getVarValueColor($1); + } else { + YYERROR; + } +} + +varFile: +VARIABLE { + if (checkVarUse(ctx, @1, $1, ifpp::VAR_FILE)) { + $$ = ctx.getVarValueFile($1); + } else { + YYERROR; + } +} + +varList: +VARIABLE { + if (checkVarUse(ctx, @1, $1, ifpp::VAR_LIST)) { + $$ = ctx.getVarValueList($1); + } else { + listNew($$); + } +} + +varStyle: +VARIABLE { + if (checkVarUse(ctx, @1, $1, ifpp::VAR_STYLE)) { + $$ = ctx.getVarValueStyle($1); + } else { + YYERROR; + } +} + +%% diff --git a/src/ParserWrapper.cpp b/src/ParserWrapper.cpp new file mode 100644 index 0000000..cd18443 --- /dev/null +++ b/src/ParserWrapper.cpp @@ -0,0 +1,36 @@ +#include "ParserWrapper.h" + +#include +#include +#include + +// Autogenerated files are not super strict. +#pragma GCC diagnostic ignored "-Weffc++" +#include "Parser.h" +#include "Lexer.h" + +namespace ifpp { + +void parse(Context & ctx, Logger & log, bool debugLex = false, bool debugParse = false) { + if (!(yyin = fopen(ctx.file.c_str(), "r"))) { + std::stringstream ss; + ss << "Unable to open input file \"" << ctx.file << "\"!" << std::endl; + ss << "Reason: " << strerror(errno); + throw std::runtime_error(ss.str()); + } + + yy::Parser parser(ctx); + + yyset_debug(debugLex); + parser.set_debug_level(debugParse); + + log.message() << "Parser initialized." << std::endl; + log.message() << "Parsing file \"" << ctx.file << "\"..." << std::endl; + + int result = parser.parse(); + fclose(yyin); + + if (result != 0) throw std::runtime_error("Parser finished with an error."); +} + +} \ No newline at end of file diff --git a/src/ParserWrapper.h b/src/ParserWrapper.h new file mode 100644 index 0000000..f6cbe81 --- /dev/null +++ b/src/ParserWrapper.h @@ -0,0 +1,15 @@ +#ifndef IFPP_PARSER_WRAPPER_H +#define IFPP_PARSER_WRAPPER_H + +#include "Context.h" +#include "Logger.h" + +// This allows us to call generated lexer and parser while not having them to compile with strict GCC options. + +namespace ifpp { + +void parse(Context & ctx, Logger & log, bool debugLex, bool debugParse); + +} + +#endif \ No newline at end of file diff --git a/src/RuleOperations.cpp b/src/RuleOperations.cpp new file mode 100644 index 0000000..07f0e62 --- /dev/null +++ b/src/RuleOperations.cpp @@ -0,0 +1,560 @@ +#include "RuleOperations.h" + +namespace ifpp { + +static bool MatchedBy(const std::string & s1, const std::string & s2) { + return s1.find(s2) != std::string::npos; +} + +/*********** +* CONTAINMENT - CONDITIONS +***********/ + +static bool ConditionSubset(const ConditionInterval * first, const ConditionInterval * second) { + return second->from <= first->from && first->to <= second->to; +} + +static bool ConditionSubset(const ConditionBool * first, const ConditionBool * second) { + return first->value == second->value; +} + +static bool ConditionSubset(const ConditionNameList * first, const ConditionNameList * second) { + // True if every string in the first list is matched by some string in the second list. + for (const auto & s1 : first->nameList) { + bool found = false; + for (const auto & s2 : second->nameList) { + if (MatchedBy(s1, s2)) { + // Anything matching s1 can also be matched by s2. + found = true; + break; + } + } + if (!found) return false; // This string can not be matched by the second list. + } + return true; +} + +static bool ConditionSubset(const ConditionSocketGroup * first, const ConditionSocketGroup * second) { + // True if the first condition needs fewer or equal sockets of every color. + if (first->socketGroup.r > second->socketGroup.r) return false; + if (first->socketGroup.g > second->socketGroup.g) return false; + if (first->socketGroup.b > second->socketGroup.b) return false; + if (first->socketGroup.w > second->socketGroup.w) return false; + return true; +} + +bool ConditionSubset(const Condition * first, const Condition * second) { + if (!first || !second) { + throw InternalError("Attempting to test containment of a NULL condition!", __FILE__, __LINE__); + } + if (first->what != second->what) { + throw InternalError("Attempting to test containment of conditions of different type!", __FILE__, __LINE__); + } + switch (first->conType) { + case CON_INTERVAL: + return ConditionSubset(static_cast(first), static_cast(second)); + case CON_BOOL: + return ConditionSubset(static_cast(first), static_cast(second)); + case CON_NAMELIST: + return ConditionSubset(static_cast(first), static_cast(second)); + case CON_SOCKETGROUP: + return ConditionSubset(static_cast(first), static_cast(second)); + default: + throw InternalError("Unknown condition type!", __FILE__, __LINE__); + } +} + +/*********** +* INTERSECTION - CONDITIONS +***********/ + +static Condition * ConditionIntersection(const ConditionNameList * first, const ConditionNameList * second) { + static NameList nl; + nl.clear(); + + for (const std::string & name1 : first->nameList) { + for (const std::string & name2 : second->nameList) { + // Add the more restrictive (longer) string. + std::string toAdd = ""; + if (MatchedBy(name1, name2)) toAdd = name1; + if (MatchedBy(name2, name1)) toAdd = name2; + if (toAdd != "") { + // See if we actually need to add it, or if we need to refine some other name in the list. + // Note that the new name is *added* to the list, not intersected with it! + for (auto it = nl.begin(); it != nl.end(); ) { + if (MatchedBy(toAdd, *it)) { + // The string is already matched by some other, longer, name in the list. + toAdd = ""; + break; + } + if (MatchedBy(*it, toAdd)) { + // This name in the list is useless, as the newly added string matches it anyway. + it = nl.erase(it); + } else { + ++it; + } + } + if (toAdd != "") { + nl.push_back(toAdd); + } + } + } + } + + if (nl.empty()) { + return NULL; + } else { + return new ConditionNameList(first->what, nl); + } +} + +Condition * ConditionIntersection(const Condition * first, const Condition * second) { + if (!first || !second) { + throw InternalError("Attempting to take an intersection with an empty condition!", __FILE__, __LINE__); + } + if (first->what != second->what) { + throw InternalError("Attempting to take an intersection of conditions of different type!", __FILE__, __LINE__); + } + + switch (first->conType) { + // Interval and bool are computed just by adding them to a rule. + // SocketGroup is not handled yet (likely not worth the effort?) + case CON_INTERVAL: + throw InternalError("Computing an intersection of conditions when not needed!", __FILE__, __LINE__); + case CON_BOOL: + throw InternalError("Computing an intersection of conditions when not needed!", __FILE__, __LINE__); + case CON_NAMELIST: + return ConditionIntersection(static_cast(first), static_cast(second)); + case CON_SOCKETGROUP: + throw InternalError("Computing an intersection of conditions when not needed!", __FILE__, __LINE__); + default: throw InternalError("Unknown condition type!", __FILE__, __LINE__); + } +} + +/*********** +* INTERSECTION - RULES +***********/ + +template +static bool contains(const std::vector & v, const T & t) { + for (const auto & x : v) if (t == x) return true; + return false; +} + +// We deal with these conditions differently; see the comments for ConditionIntersect. +static const std::vector special = {"Class", "BaseType"}; + +RuleNative * RuleIntersection(const RuleNative * first, const RuleNative * second) { + if (first->hasMod(MOD_FINAL)) { + // First rule is Final and can not be overridden. + return const_cast(first); + } + + // Compute the intersection. + // Preserve modifiers - Final. + // At this point Show or Hide do not exist, + // they get deleted when we transform an IFPP rule into a native one and then rebuilt when we print it. + RuleNative * result = new RuleNative(second->hasMod(MOD_FINAL) ? MOD_FINAL : 0); + + // All other conditions are intersected by simply adding them to the rule. + for (const auto & c : first->conditions) { + if (contains(special, c.first)) continue; + result->addCondition(c.second); + if (result->useless) { + goto intersection_empty; + } + } + + for (const auto & c : second->conditions) { + if (contains(special, c.first)) continue; + result->addCondition(c.second); + if (result->useless) { + goto intersection_empty; + } + } + + // Deal with special NameList conditions. + for (const std::string & what : special) { + const auto & r1 = first->conditions.equal_range(what); + const auto & r2 = second->conditions.equal_range(what); + + if (r1.first == r1.second && r2.first == r2.second) { + // Neither rule has a condition of this type. + } + else if (r1.first == r1.second) { + // Only the second rule has conditions of this type. + for (auto it = r2.first; it != r2.second; ++it) { + result->addCondition(it->second); + } + } + else if (r2.first == r2.second) { + // Only the first rule has conditions of this type. + for (auto it = r1.first; it != r1.second; ++it) { + result->addCondition(it->second); + } + } + else { + // Both rules have matching conditions. + // Intersect every condition of the first rule with every condition of the second rule. + // Then add all these intersections to the new rule. + // This will in reasonable cases (one condition per rule) not lead to a blowup in the number of conditions. + // See comments for ConditionIntersection for details on why and how we do this. + for (auto it1 = r1.first; it1 != r1.second; ++it1) { + for (auto it2 = r2.first; it2 != r2.second; ++it2) { + Condition * c = ConditionIntersection(it1->second, it2->second); + if (c) { + result->addCondition(c); + } else { + // This condition does not match anything. + // Since a rule is an intersection of all conditions, it does not match anything either. + goto intersection_empty; + } + } + } + } + } + + { + // Here we know that the intersection matches some items. + // We want to know if it actually changes the rule it overrides. + // It might not, because all of first's actions are Final, or all of second's actions are Append. + bool changed = false; + if (second->hasMod(MOD_FINAL)) { + // The new rule adds a Final modifier to the previous rule. + changed = true; + } + + // Compute the actions according to their modifiers. + // Collect all the actions of both rules together. + std::map > actions; + + for (const auto & a : first->actions) { + auto it = actions.find(a.first); + if (it == actions.end()) { + actions.insert(std::make_pair(a.first, std::make_pair(a.second, (Action *)NULL))); + } else { + it->second.first = a.second; + } + } + + for (const auto & a : second->actions) { + auto it = actions.find(a.first); + if (it == actions.end()) { + actions.insert(std::make_pair(a.first, std::make_pair((Action *)NULL, a.second))); + } else { + it->second.second = a.second; + } + } + + for (const auto & a : actions) { + const Action * a1 = a.second.first; + const Action * a2 = a.second.second; + + if (second->hasMod(MOD_OVERRIDE)) { + if (a1 && a1->hasMod(MOD_FINAL)) { + // First action is final, do not change. + result->addAction(a1); + } else if (a2) { + // Override action in first with an action in second. + // TODO: if the action has the same parameters there is actually no change. + result->addAction(a2); + changed = true; + } + } else { + // Second rule is Append. + if (!a1 && a2) { + // First action is not defined, second is. + result->addAction(a2); + changed = true; + } else if (a1 && a1->hasMod(MOD_FINAL)) { + // First action is Final. + result->addAction(a1); + } else if (a2 && a2->hasMod(MOD_OVERRIDE)) { + // Second action is Override. + result->addAction(a2); + changed = true; + } else { + // First action is defined and not Final, second action is not Override. + result->addAction(a1); + } + } + } + + if (changed) { + // A new, useful rule. + return result; + } + else { + // The new rule is redundant, as it does not modify the first rule. + // But the first rule might still reduce the second rule in terms of conditions. + delete result; + return const_cast(first); + } + } + +intersection_empty: + // The two rules do not intersect, and do not interact with each other. + delete result; + return NULL; +} + +/*********** +* DIFFERENCE - CONDITIONS +***********/ + +static std::pair ConditionDifference(const ConditionInterval * first, const ConditionInterval * second) { + static const auto empty = std::make_pair(EMPTY, (ConditionInterval *)NULL); + static const auto exactlyFirst = std::make_pair(FIRST, (ConditionInterval *)NULL); + static const auto invalid = std::make_pair(INVALID, (ConditionInterval *)NULL); + + if (!first) { + if (second->from == INT_MIN && second->to == INT_MAX) { + // This shouldn't happen? + return empty; + } else if (second->from == INT_MIN) { + return std::make_pair(NEW, new ConditionInterval(second->what, second->to + 1, INT_MAX)); + } else if (second->to == INT_MAX) { + return std::make_pair(NEW, new ConditionInterval(second->what, INT_MIN, second->from - 1)); + } else { + return invalid; + } + } + + if (second->to < first->from) { + // |---| + // |---| + return exactlyFirst; + } + else if (first->to < second->from) { + // |---| + // |---| + return exactlyFirst; + } + else if (second->from <= first->from && first->to <= second->to) { + // |---| + // |-------| + return empty; + } + else if (first->from < second->from && second->to < first->to) { + // |-------| + // |---| + return invalid; + } + else if (second->from <= first->from && second->to < first->to) { + // |-----| + // |-----| + return std::make_pair(NEW, new ConditionInterval(first->what, second->to + 1, first->to)); + } + else if (first->from < second->from && first->to <= second->to) { + // |-----| + // |-----| + return std::make_pair(NEW, new ConditionInterval(first->what, first->from, second->from - 1)); + } + + throw InternalError("Undefined behavior when taking difference of intervals!", __FILE__, __LINE__); +} + +static std::pair ConditionDifference(const ConditionBool * first, const ConditionBool * second) { + if (!first) { + return std::make_pair(NEW, new ConditionBool(second->what, !second->value)); + } + if (first->value != second->value) { + return std::make_pair(FIRST, (ConditionBool *)NULL); + } + if (first->value == second->value) { + return std::make_pair(EMPTY, (ConditionBool *)NULL); + } + throw InternalError("Undefined behavior when taking difference of bool conditions!", __FILE__, __LINE__); +} + +static std::pair ConditionDifference(const ConditionNameList * first, const ConditionNameList * second) { + if (!first) { + return std::make_pair(INVALID, (ConditionSocketGroup *)NULL); + } + + // Keep those names from first which are not matched by second. + // This might be an overestimation, but that is okay. + // (We are not "cheating" as we do with intersections.) + static NameList nl; + nl.clear(); + + for (const std::string & name1 : first->nameList) { + bool add = true; + for (const std::string & name2 : second->nameList) { + if (MatchedBy(name1, name2)) { + add = false; + break; + } + } + if (add) nl.push_back(name1); + } + + if (nl.empty()) { + return std::make_pair(EMPTY, (Condition *)NULL); + } + else if (nl.size() == first->nameList.size()) { + return std::make_pair(FIRST, (Condition *)NULL); + } else { + return std::make_pair(NEW, new ConditionNameList(first->what, nl)); + } +} + +static std::pair ConditionDifference(const ConditionSocketGroup * first, const ConditionSocketGroup * second) { + if (!first) { + return std::make_pair(INVALID, (ConditionSocketGroup *)NULL); + } + if (ConditionSubset(first, second)) { + return std::make_pair(EMPTY, (ConditionSocketGroup *)NULL); + } + // We can't say much otherwise. + return std::make_pair(FIRST, (ConditionSocketGroup *)NULL); +} + +std::pair ConditionDifference(const Condition * first, const Condition * second) { + if (!second) { + throw InternalError("Attempting to take difference of an empty condition!", __FILE__, __LINE__); + } + if (first && first->what != second->what) { + throw InternalError("Attempting to take difference of conditions of different type!", __FILE__, __LINE__); + } + + switch (second->conType) { + case CON_INTERVAL: + return ConditionDifference(static_cast(first), static_cast(second)); + case CON_BOOL: + return ConditionDifference(static_cast(first), static_cast(second)); + case CON_NAMELIST: + return ConditionDifference(static_cast(first), static_cast(second)); + case CON_SOCKETGROUP: + return ConditionDifference(static_cast(first), static_cast(second)); + default: throw InternalError("Unknown condition type!", __FILE__, __LINE__); + } +} + +/*********** +* DIFFERENCE - RULES +***********/ + +RuleNative * RuleDifference(const RuleNative * first, const RuleNative * second) { +/* +Caution: incoming bunch of math. + +Let first have conditions a1, a2, a3 and second have conditions b1, b2, b3. +Therefore R1 matches a1 /\ a2 /\ a3, R2 matches b1 /\ b2 /\ b3. +We want to compute R1 - R2. + + R1 - R2 += (a1 /\ a2 /\ a3) - (b1 /\ b2 /\ b3) += (a1 /\ a2 /\ a3) /\ (b1 /\ b2 /\ b3)' += (a1 /\ a2 /\ a3) /\ (b1' \/ b2' \/ b3') += (a1 /\ a2 /\ a3 /\ b1') + \/ (a1 /\ a2 /\ a3 /\ b2') + \/ (a1 /\ a2 /\ a3 /\ b3') + +We can not (in general) compute unions of rules; and we do not want to make more rules for this. +Even trying to do the union only makes sense if b_i and b_j are the same condition; which will generally not happen. +But if at most one of the members of the unions is non-empty, we can keep only that part. +Also, if some of the members is exactly R1, the whole union is R1 and we can return it. +(This happens if some a_i and b_i; and thus R1 and R2, are disjoint.) +This should solve several common cases - especially if R2 has only one condition. + +Another "computable" case would be if all the unions are equal, and thus equal to R1. +(In other words, R1 and R2 are completely disjoint?) +But this case will be handled automatically since we return first if there is more than one non-empty component. + +Only testing will show how complete this is. +*/ + int count = 0; // How many parts of the union are non-trivial. + Condition * which = NULL; // The new condition that we would add. + + for (const auto & c2 : second->conditions) { + if (second->conditions.count(c2.first) > 1) { + // More than one of this type of condition in R2. + // This will only happen with NameList and SocketGroup. + // We leave this alone... for now. + goto return_first; + } + + const Condition * c1 = NULL; + switch (first->conditions.count(c2.first)) { + // How many conditions of the same name do we have in the first rule? + case 0: + // If we can compute b_i', we can simply add it as a new condition. + c1 = NULL; + break; + case 1: + // Calculate a_i /\ b_i' = a_i - b_i. + c1 = first->conditions.find(c2.first)->second; + break; + default: + // More than one of this type of condition in R2. + // This will only happen with NameList and SocketGroup. + // We leave this alone... for now. + goto return_first; + } + + auto diff = ConditionDifference(c1, c2.second); + switch (diff.first) { + case EMPTY: + // This part of the union is empty. We're good. + continue; + case FIRST: + // This part of the union matches all of R1. Thus the entire union will also match all of R1. + // (This means that the conditions c1 and c2, and thus R1 and R2, are disjoint.) + goto return_first; + case NEW: + // This part is non-empty, and we have a condition matching it. + ++count; + if (which) delete which; + which = diff.second; + break; + case INVALID: + // This part is non-empty, but we can't compute it. + ++count; + if (which) delete which; + which = NULL; // Do not change the first rule. + break; + default: + throw InternalError("Unknown result of condition difference!", __FILE__, __LINE__); + } + } + + if (count == 0) { + // All components of the union are empty, thus the difference is empty as well. + return NULL; + } + else if (count == 1) { + // One of the components is non-empty. + // Return a copy of first, with the matching condition a_i replaced by the condition b_i that defines this part. + if (which == NULL) { + // The difference is not a valid condition, there is nothing we can do. + goto return_first; + } + + // We do not use addCondition here, because we are potentially modifying a NameList condition. + RuleNative * result = first->clone(); + auto it = result->conditions.find(which->what); + if (it != result->conditions.end()) { + // Replace the condition. + delete it->second; + it->second = which; + } else { + // Add a new condition. + result->conditions.insert(std::make_pair(which->what, which)); + } + return result; + } + else { + // More than one component is non-empty; but none of them is all of R1. + // We can't define the difference exactly. + goto return_first; + } + + throw InternalError("Undefined behavior in computing difference of rules!", __FILE__, __LINE__); + +return_first: + if (which) delete which; + return const_cast(first); +} + +} \ No newline at end of file diff --git a/src/RuleOperations.h b/src/RuleOperations.h new file mode 100644 index 0000000..685c342 --- /dev/null +++ b/src/RuleOperations.h @@ -0,0 +1,90 @@ +#ifndef IFPP_RULE_OPERATIONS_H +#define IFPP_RULE_OPERATIONS_H + +#include "ifppTypes.h" +#include "ifppCompiler.h" + +namespace ifpp { + +/* +Returns true if the first condition matches a subset of the second. +Assumes both inputs are non-NULL. +*/ +bool ConditionSubset(const Condition * first, const Condition * second); + +/* +Returns a condition obtained as an intersection of two conditions. +Assumes both inputs are non-NULL. + +Always returns a new condition. +If the intersection doesn't match anything, returns NULL. + +For most conditions, adding them to a rule behaves like intersecting them. +RuleNative is smart enough to trim conditions of the same type. +So we do not need this function (and using it is treated as an internal error). + +But we deal with Class and BaseType separately to avoid generating many useless rules. +The assumption here is that the matching is done on the *input* strings, rather than all possible strings. +Thus two strings which are incomparable as substrings are assumed to not match the same string. +For example, the intersection of BaseType "Scroll" and BaseType "Wisdom" does not match any items. +This can exclude some valid matches (such as the example above), but for "reasonable" use cases +it leads to much fewer useless rules: consider a different style for every currency item. + +Note that we do not do this for HasExplicitMod, as it is quite reasonable for an item to match +multiple such conditions simultaneously - when searching for an item with two or more different mods. + +Returns NULL if the intersection of the conditions is empty (according to the above rules). +*/ +Condition * ConditionIntersection(const Condition * first, const Condition * second); + +/* +Returns a rule obtained as an intersection of two rules. +The intersection is always exact (but see above for intersecting NameLists). + +Returns NULL if the two rules do not intersect at all. +Returns first if the two rules potentially intersect, but the intersection does not need a separate rule. + This might be because first is Final, or because second does not override any action in first. +Returns a new rule otherwise. + +Conditions in the returned rule are an exact intersection of the two rules' conditions. +Actions are resolved according to the actions' modifiers. +*/ +RuleNative * RuleIntersection(const RuleNative * first, const RuleNative * second); + +/* +Computes a difference of the conditions first - second. +If the first input is NULL, we assume there is no condition (i.e. matches everything). +This lets us compute the complement of a condition. + +Output is: +first: + EMPTY if the difference is empty (matches nothing). + FIRST if the difference is exactly first. + NEW if the difference is a condition distinct from first. + INVALID if the difference is not a valid condition (e.g. not an interval for interval conditions). + +second: + If first is NEW, second is a new condition matching the interval. + Otherwise second is NULL. +*/ +enum DifferenceResult { EMPTY, FIRST, NEW, INVALID }; +std::pair ConditionDifference(const Condition * first, const Condition * second); + +/* +Returns a rule obtained as the difference first - second. +This can not always be computed exactly (conditions are not closed under complement). +Thus we result an overestimate: (first - second) <= result <= first. (Subset relations.) + +Returns NULL if the difference is empty - the second condition matches everything the first does. +Returns first if the result is first - either because the rules do not intersect, or because we can not give a better estimate. +Returns a new rule otherwise. + +This should *not* ignore first if first is final. +We use it to restrict the rule being added, which can be final. +Actions in the new rule always copy actions in first. +*/ +RuleNative * RuleDifference(const RuleNative * first, const RuleNative * second); + +} + +#endif \ No newline at end of file diff --git a/src/Types.cpp b/src/Types.cpp new file mode 100644 index 0000000..c31cc4e --- /dev/null +++ b/src/Types.cpp @@ -0,0 +1,413 @@ +#include "Types.h" + +#include +#include +#include + +namespace ifpp { + +int IFPP_GUID() { + static volatile int id = 0; + return id++; +} + +int IFPP_TABS = 0; + +/*********** +* BASE TYPES +***********/ + +std::ostream & operator<<(std::ostream & os, const Rarity & r) { + switch (r) { + case Normal: return os << "Normal"; + case Magic: return os << "Magic"; + case Rare: return os << "Rare"; + case Unique: return os << "Unique"; + default: throw UnhandledCase("Rarity", __FILE__, __LINE__); + } +} + +Color::Color(const std::string & hexValue) : r(0), g(0), b(0), a(0) { + int x = strtol(hexValue.c_str(), NULL, 16); + switch (hexValue.size()) { + case 3: // rgb + r = (x & 0xF00) >> 8; r |= r << 4; + g = (x & 0x0F0) >> 4; g |= g << 4; + b = x & 0x00F; b |= b << 4; + a = 255; + break; + case 4: // rgba + r = (x & 0xF000) >> 12; r |= r << 4; + g = (x & 0x0F00) >> 8; g |= g << 4; + b = (x & 0x00F0) >> 4; b |= b << 4; + a = x & 0x000F; a |= a << 4; + break; + case 6: // rrggbb + r = (x & 0xFF0000) >> 16; + g = (x & 0x00FF00) >> 8; + b = x & 0x0000FF; + a = 255; + break; + case 8: // rrggbbaa + r = (x & 0xFF000000) >> 24; + g = (x & 0x00FF0000) >> 16; + b = (x & 0x0000FF00) >> 8; + a = x & 0x000000FF; + break; + default: + throw InternalError("Invalid hex color value: " + hexValue, __FILE__, __LINE__); + } +} + +std::ostream & operator<<(std::ostream & os, const Color & c) { + return os << c.r << ' ' << c.g << ' ' << c.b << ' ' << c.a; +} + +SocketGroup::SocketGroup(const std::string & sockets) : r(0), g(0), b(0), w(0) { + for (size_t i = 0; i < sockets.size(); ++i) { + switch (sockets[i]) { + case 'r': case 'R': ++r; break; + case 'g': case 'G': ++g; break; + case 'b': case 'B': ++b; break; + case 'w': case 'W': ++w; break; + default: + throw InternalError("Invalid socket color!", __FILE__, __LINE__); + } + } +} + +std::ostream & operator<<(std::ostream & os, const SocketGroup & sg) { + return os + << std::string(sg.r, 'R') + << std::string(sg.g, 'G') + << std::string(sg.b, 'B') + << std::string(sg.w, 'W'); +} + +std::ostream & operator<<(std::ostream & os, const NameList & nl) { + bool first = true; + for (auto && name : nl) { + if (!first) os << ' '; + first = false; + os << '"' << name << '"'; + } + return os; +} + +std::string modString(ModifierList ml) { + if (ml == MOD_OVERRIDE) return "Override"; + if (ml == MOD_APPEND) return "Append"; + if (ml == MOD_FINAL) return "Final"; + if (ml == MOD_ADDONLY) return "Final"; + if (ml == MOD_SHOW) return "Show"; + if (ml == MOD_HIDE) return "Hide"; + throw InternalError("Attempting to convert unknown or compound modifier to string!", __FILE__, __LINE__); +} + +std::ostream & print(std::ostream & os, PrintStyle ps, ModifierList ml) { + switch (ps) { + case PRINT_IFPP: + if (ml & MOD_OVERRIDE) os << "Override "; + if (ml & MOD_APPEND) os << "Append "; + if (ml & MOD_FINAL) os << "Final "; + if (ml & MOD_ADDONLY) os << "AddOnly "; + if (ml & MOD_SHOW) os << "Show "; + if (ml & MOD_HIDE) os << "Hide "; + return os; + case PRINT_NATIVE: throw InternalError("Attempting to write modifiers to native filter!", __FILE__, __LINE__); + default: throw UnhandledCase("Print style", __FILE__, __LINE__); + } +} + + + +/*********** +* ENUMS +***********/ + +std::ostream & operator<<(std::ostream & os, VariableType vt) { + switch (vt) { + case VAR_NUMBER: return os << "Number"; + case VAR_COLOR: return os << "Color"; + case VAR_FILE: return os << "File"; + case VAR_LIST: return os << "List"; + case VAR_STYLE: return os << "Style"; + case VAR_UNDEFINED: return os << "Undefined"; + default: throw UnhandledCase("Variable type", __FILE__, __LINE__); + } +} + +std::ostream & operator<<(std::ostream & os, Operator o) { + switch (o) { + case OP_LT: return os << '<'; + case OP_LE: return os << "<="; + case OP_EQ: return os << '='; + case OP_GE: return os << ">="; + case OP_GT: return os << '>'; + default: throw UnhandledCase("Operator", __FILE__, __LINE__); + } +} + +std::ostream & Command::printSelf(std::ostream & os, PrintStyle ps) const { + switch (ps) { + case PRINT_IFPP: return os << std::string(IFPP_TABS, '\t') << '[' << guid << "] "; + case PRINT_NATIVE: return os << std::string(IFPP_TABS, '\t'); + default: throw UnhandledCase("Print style", __FILE__, __LINE__); + } +} + +/*********** +* CONDITIONS +***********/ + +std::ostream & Condition::printSelf(std::ostream & os, PrintStyle ps) const { + return Command::printSelf(os, ps) << what; +} + +std::ostream & ConditionInterval::printSelf(std::ostream & os, PrintStyle ps) const { + if (what == "Rarity") { + if (from > to) throw InternalError("Condition " + what + " has inverted range!", __FILE__, __LINE__); + if (to < Normal || from > Unique) throw InternalError("Condition " + what + " does not match any value!", __FILE__, __LINE__); + if (from == INT_MIN && to == INT_MAX) throw InternalError("Condition " + what + " matches all possible values!", __FILE__, __LINE__); + if (from == INT_MIN) return Condition::printSelf(os, ps) << " <= " << (Rarity)to << std::endl; + if (to == INT_MAX) return Condition::printSelf(os, ps) << " >= " << (Rarity)from << std::endl; + if (from == to) return Condition::printSelf(os, ps) << " = " << (Rarity)from << std::endl; + + switch (ps) { + case PRINT_IFPP: + return Condition::printSelf(os, ps) << ' ' << (Rarity)from << " .. " << (Rarity)to << std::endl; + case PRINT_NATIVE: + Condition::printSelf(os, ps) << " >= " << (Rarity)from << std::endl; + Condition::printSelf(os, ps) << " <= " << (Rarity)to << std::endl; + return os; + default: + throw UnhandledCase("Print style", __FILE__, __LINE__); + } + } + else { + if (from > to) throw InternalError("Condition " + what + " has inverted range!", __FILE__, __LINE__); + if (to < 0) throw InternalError("Condition " + what + " does not match any value!", __FILE__, __LINE__); + if (from == INT_MIN && to == INT_MAX) throw InternalError("Condition " + what + " matches all possible values!", __FILE__, __LINE__); + if (from == INT_MIN) return Condition::printSelf(os, ps) << " <= " << to << std::endl; + if (to == INT_MAX) return Condition::printSelf(os, ps) << " >= " << from << std::endl; + if (from == to) return Condition::printSelf(os, ps) << " = " << from << std::endl; + + switch (ps) { + case PRINT_IFPP: + return Condition::printSelf(os, ps) << ' ' << from << " .. " << to << std::endl; + case PRINT_NATIVE: + Condition::printSelf(os, ps) << " >= " << from << std::endl; + Condition::printSelf(os, ps) << " <= " << to << std::endl; + return os; + default: + throw UnhandledCase("Print style", __FILE__, __LINE__); + } + } +} + +ConditionInterval * ConditionInterval::clone() const { + return new ConditionInterval(what, from, to); +} + +std::ostream & ConditionNameList::printSelf(std::ostream & os, PrintStyle ps) const { + return Condition::printSelf(os, ps) << ' ' << nameList << std::endl; +} + +ConditionNameList * ConditionNameList::clone() const { + return new ConditionNameList(what, nameList); +} + +std::ostream & ConditionSocketGroup::printSelf(std::ostream & os, PrintStyle ps) const { + return Condition::printSelf(os, ps) << ' ' << socketGroup << std::endl; +} + +ConditionSocketGroup * ConditionSocketGroup::clone() const { + return new ConditionSocketGroup(what, socketGroup); +} + +std::ostream & ConditionBool::printSelf(std::ostream & os, PrintStyle ps) const { + return Condition::printSelf(os, ps) << (value ? " true" : " false") << std::endl; +} + +ConditionBool * ConditionBool::clone() const { + return new ConditionBool(what, value); +} + + + +/*********** +* ACTIONS +***********/ + +std::ostream & Action::printSelf(std::ostream & os, PrintStyle ps) const { + Command::printSelf(os, ps); + switch (ps) { + case PRINT_IFPP: print(os, ps, modifiers); break; + case PRINT_NATIVE: break; + default: throw UnhandledCase("Print style", __FILE__, __LINE__); + } + return os << what; +} + +bool Action::hasMod(ModifierList ml) const { + return modifiers & ml; +} + +std::ostream & operator<<(std::ostream & os, const Style & s) { + for (const auto & a : s) { + a->printSelf(os, PRINT_IFPP); + } + return os; +} + +std::ostream & DefaultStyle::printSelf(std::ostream & os, PrintStyle ps) const { + switch (ps) { + case PRINT_IFPP: + Command::printSelf(os, ps); + os << what << '{' << std::endl; + ++IFPP_TABS; + print(os, ps, style); + --IFPP_TABS; + os << std::string(IFPP_TABS, '\t') << '}' << std::endl; + return os; + case PRINT_NATIVE: + throw InternalError("Attempting to print a Default action to native filter!", __FILE__, __LINE__); + default: + throw UnhandledCase("Print style", __FILE__, __LINE__); + } +} + +DefaultStyle * DefaultStyle::clone() const { + Style s; + for (const auto & a : style) s.push_back(a->clone()); + return new DefaultStyle(s); +} + + +/*********** +* STATEMENTS +***********/ + +std::ostream & Statement::printSelf(std::ostream & os, PrintStyle ps) const { + switch (ps) { + case PRINT_IFPP: return os << std::string(IFPP_TABS, '\t') << "[" << guid << "] "; + case PRINT_NATIVE: return os << std::string(IFPP_TABS, '\t'); + default: throw UnhandledCase("Print style", __FILE__, __LINE__); + } +} + +std::ostream & print(std::ostream & os, PrintStyle ps, const FilterIFPP & f) { + bool first = true; + for (const auto & stm : f) { + if (!first) os << std::endl; + first = false; + print(os, ps, stm); + } + return os; +} + +std::ostream & Instruction::printSelf(std::ostream & os, PrintStyle ps) const { + switch (ps) { + case PRINT_IFPP: return Statement::printSelf(os, ps) << what; + case PRINT_NATIVE: throw InternalError("Attempting to write IFPP-only instruction to native filter!", __FILE__, __LINE__); + default: throw UnhandledCase("Print style", __FILE__, __LINE__); + } +} + +std::ostream & InstructionFlush::printSelf(std::ostream & os, PrintStyle ps) const { + return Instruction::printSelf(os, ps) << std::endl; +} + +InstructionFlush * InstructionFlush::clone() const { + return new InstructionFlush(); +} + +std::ostream & InstructionVersion::printSelf(std::ostream & os, PrintStyle ps) const { + return Instruction::printSelf(os, ps) << ' ' << vMajor << '.' << vMinor << '.' << vPatch << std::endl; +} + +InstructionVersion * InstructionVersion::clone() const { + return new InstructionVersion(vMajor, vMinor, vPatch); +} + + +std::ostream & DefinitionBase::printSelf(std::ostream & os, PrintStyle ps) const { + switch (ps) { + case PRINT_IFPP: return Statement::printSelf(os, ps) << "Define " << varName << ' ' << varType; + case PRINT_NATIVE: throw InternalError("Attempting to write a variable definition to native filter!", __FILE__, __LINE__); + default: throw UnhandledCase("Print style", __FILE__, __LINE__); + } +} + +std::ostream & RuleIFPP::printSelf(std::ostream & os, PrintStyle ps) const { + switch (ps) { + case PRINT_IFPP: + Statement::printSelf(os, ps) << "Rule "; + print(os, ps, modifiers) << '{' << std::endl; + ++IFPP_TABS; + print(os, ps, commands); + --IFPP_TABS; + os << std::string(IFPP_TABS, '\t') << '}' << std::endl; + return os; + case PRINT_NATIVE: throw InternalError("Attempting to write an IFPP rule to native filter!", __FILE__, __LINE__); + default: throw UnhandledCase("Print style", __FILE__, __LINE__); + } +} + +RuleIFPP * RuleIFPP::clone() const { + RuleIFPP * r = new RuleIFPP(modifiers); + r->commands.reserve(commands.size()); + for (const auto & c : commands) { + r->commands.push_back(c->clone()); + } + return r; +} + +bool RuleIFPP::hasMod(ModifierList ml) const { + return modifiers & ml; +} + +RuleIFPP::~RuleIFPP() { + for (const auto & c : commands) delete c; +} + +int getLimit(const std::string & what, WhichLimit which) { + static std::map > limits; + static std::map defaults; + static bool first = true; + if (first) { + first = false; + limits["ItemLevel"] = std::make_pair(1, 100); + limits["DropLevel"] = std::make_pair(1, 100); + limits["Quality"] = std::make_pair(0, 30); + limits["Sockets"] = std::make_pair(0, 6); // Kaom's stuff has 0 sockets + limits["LinkedSockets"] = std::make_pair(0, 6); // Kaom's stuff has 0 sockets + limits["Height"] = std::make_pair(1, 4); + limits["Width"] = std::make_pair(1, 2); + limits["StackSize"] = std::make_pair(1, 1000); // Perandus Coins? + limits["GemLevel"] = std::make_pair(1, 21); // Don't think you can go over this. + limits["Rarity"] = std::make_pair(1, 4); // Normal, Magic, Rare, Unique + limits["MapTier"] = std::make_pair(1, 16); // Shaper's Realm (T17) is not a map? + + limits["SetFontSize"] = std::make_pair(17, 45); // https://www.pathofexile.com/forum/view-thread/2199068 + limits["Color"] = std::make_pair(0, 255); + limits["Volume"] = std::make_pair(0, 300); + limits["MinimapIcon"] = std::make_pair(0, 2); + + defaults["Color"] = 255; + defaults["Volume"] = 100; // ??? + defaults["FontSize"] = 33; + } + try { + switch (which) { + case MIN: return limits.at(what).first; + case MAX: return limits.at(what).second; + case DEFAULT: return defaults.at(what); + default: throw UnhandledCase("Limit type", __FILE__, __LINE__); + } + } catch (std::exception & e) { + throw InternalError("Requesting unknown limit value of " + what + "!", __FILE__, __LINE__); + } +} + +} diff --git a/src/Types.h b/src/Types.h new file mode 100644 index 0000000..f021fd5 --- /dev/null +++ b/src/Types.h @@ -0,0 +1,371 @@ +#ifndef IFPP_TYPES_H +#define IFPP_TYPES_H + +#include +#include +#include +#include +#include + +namespace ifpp { + +struct InternalError : public std::logic_error { + const std::string file; + int line; + + InternalError(const std::string & what, const std::string & f, int l) : std::logic_error(what), file(f), line(l) {} +}; + +struct UnhandledCase : public InternalError { + UnhandledCase(const std::string & what, const std::string & f, int l) : InternalError("Unhandled case value of " + what, f, l) {} +}; + +int IFPP_GUID(); + +extern int IFPP_TABS; + +enum PrintStyle { PRINT_IFPP, PRINT_NATIVE }; + +template +std::ostream & print(std::ostream & os, PrintStyle ps, T * t) { + if (t) { + return t->printSelf(os, ps); + } else { + return os << std::string(IFPP_TABS, '\t') << "-NULL-" << std::endl; + } +} + +template +std::ostream & print(std::ostream & os, PrintStyle ps, const T & t) { + return t.printSelf(os, ps); +} + +template +std::ostream & print(std::ostream & os, PrintStyle ps, const std::vector & v) { + for (const auto & x : v) { + print(os, ps, x); + } + return os; +} + +/*********** +* BASE TYPES +***********/ + +enum Rarity { Normal = 1, Magic = 2, Rare = 3, Unique = 4 }; +std::ostream & operator<<(std::ostream & os, const Rarity & r); + +struct Color { + int r, g, b, a; + Color() : r(0), g(0), b(0), a(255) {} + Color(int R, int G, int B, int A = 255) : r(R), g(G), b(B), a(A) {} + Color(const std::string & hexValue); +}; +std::ostream & operator<<(std::ostream & os, const Color & r); + +struct SocketGroup { + int r, g, b, w; + SocketGroup() : r(0), g(0), b(0), w(0) {} + SocketGroup(const std::string & sockets); +}; +std::ostream & operator<<(std::ostream & os, const SocketGroup & sg); + +typedef std::vector NameList; +std::ostream & operator<<(std::ostream & os, const NameList & nl); + +typedef unsigned int ModifierList; +const unsigned int MOD_OVERRIDE = 1 << 0; +const unsigned int MOD_APPEND = 1 << 1; +const unsigned int MOD_FINAL = 1 << 2; +const unsigned int MOD_ADDONLY = 1 << 3; +const unsigned int MOD_SHOW = 1 << 10; +const unsigned int MOD_HIDE = 1 << 11; +std::string modString(ModifierList ml); +std::ostream & print(std::ostream & os, PrintStyle ps, ModifierList ml); + + +/*********** +* ENUMS +***********/ + +enum StatementType { STM_DEFINITION, STM_INSTRUCTION, STM_RULE }; +enum CommandType { COM_CONDITION, COM_ACTION, COM_RULE, COM_DEFAULT }; +enum ConditionType { CON_INTERVAL, CON_RARITY, CON_BOOL, CON_NAMELIST, CON_SOCKETGROUP }; +//enum ActionType { AC_COLOR, AC_NUMBER, AC_SOUND, AC_FILENAME, AC_BOOLEAN, AC_STYLE }; + +enum VariableType { VAR_NUMBER, VAR_COLOR, VAR_FILE, VAR_LIST, VAR_STYLE, VAR_UNDEFINED }; +std::ostream & operator<<(std::ostream & os, VariableType vt); + +enum Operator { OP_LT, OP_LE, OP_EQ, OP_GE, OP_GT }; +std::ostream & operator<<(std::ostream & os, Operator o); + +struct Command { + CommandType comType; + std::string what; + int guid; + + Command(CommandType t, const std::string & w) : comType(t), what(w), guid(IFPP_GUID()) {} + virtual std::ostream & printSelf(std::ostream & os, PrintStyle ps) const; + virtual Command * clone() const = 0; + virtual ~Command() {} +}; +typedef std::vector CommandList; + + + +/*********** +* CONDITIONS +***********/ + +struct Condition : public Command { + ConditionType conType; + + Condition(ConditionType t, const std::string & w) : + Command(COM_CONDITION, w), conType(t) {} + virtual std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; +}; + +struct ConditionInterval : public Condition { + int from, to; + + ConditionInterval(const std::string & w, int f, int t) : + Condition(CON_INTERVAL, w), from(f), to(t) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; + ConditionInterval * clone() const override; +}; + +struct ConditionBool : public Condition { + bool value; + + ConditionBool(const std::string & w, bool v) : + Condition(CON_BOOL, w), value(v) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; + ConditionBool * clone() const override; +}; + +struct ConditionNameList : public Condition { + NameList nameList; + + ConditionNameList(const std::string & w, const NameList & nl) : + Condition(CON_NAMELIST, w), nameList(nl) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; + ConditionNameList * clone() const override; +}; + +struct ConditionSocketGroup : public Condition { + SocketGroup socketGroup; + + ConditionSocketGroup(const std::string & w, const SocketGroup & sg) : + Condition(CON_SOCKETGROUP, w), socketGroup(sg) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; + ConditionSocketGroup * clone() const override; +}; + + + +/*********** +* ACTIONS +***********/ + +struct Action : public Command { + ModifierList modifiers; + + Action(ModifierList m, const std::string & w) : + Command(COM_ACTION, w), modifiers(m) {} + virtual std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; + virtual Action * clone() const override = 0; + bool hasMod(ModifierList ml) const; +}; +typedef std::vector Style; +std::ostream & operator<<(std::ostream & os, const Style & s); + +template +struct Action1 : public Action { + T1 arg1; + + Action1(ModifierList m, const std::string & w, const T1 & a1) : + Action(m, w), arg1(a1) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override{ + return Action::printSelf(os, ps) << ' ' << arg1 << std::endl; + } + Action1 * clone() const override { + return new Action1(modifiers, what, arg1); + } +}; + +template<> +struct Action1 : public Action { + bool arg1; + + Action1(ModifierList m, const std::string & w, const bool & a1) : + Action(m, w), arg1(a1) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override { + switch (ps) { + case PRINT_IFPP: + return Action::printSelf(os, ps) << (arg1 ? " true" : " false") << std::endl; + case PRINT_NATIVE: + // Do not print Hidden to native filters, this is handled in compiler. + // However it is impractical to remove this action. + if (what == "Hidden") return os; + + if (arg1) { + // Print this action if it is true. + return Action::printSelf(os, ps) << std::endl; + } else { + // If it is false do nothing. + return os; + } + default: + throw UnhandledCase("Print style", __FILE__, __LINE__); + } + } + Action1 * clone() const override { + return new Action1(modifiers, what, arg1); + } +}; + +template +struct Action2 : public Action { + T1 arg1; + T2 arg2; + + Action2(ModifierList m, const std::string & w, const T1 & a1, const T2 & a2) : + Action(m, w), arg1(a1), arg2(a2) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override { + return Action::printSelf(os, ps) << ' ' << arg1 << ' ' << arg2 << std::endl; + } + Action2 * clone() const override { + return new Action2(modifiers, what, arg1, arg2); + } +}; + +template +struct Action3 : public Action { + T1 arg1; + T2 arg2; + T3 arg3; + + Action3(ModifierList m, const std::string & w, const T1 & a1, const T2 & a2, const T3 & a3) : + Action(m, w), arg1(a1), arg2(a2) , arg3(a3) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override { + return Action::printSelf(os, ps) << ' ' << arg1 << ' ' << arg2 << ' ' << arg3 << std::endl; + } + Action3 * clone() const override { + return new Action3(modifiers, what, arg1, arg2, arg3); + } +}; + +typedef Action1 ActionNumber; +typedef Action1 ActionColor; +typedef Action2 ActionSound; +typedef Action1 ActionBool; +typedef Action1 ActionFile; +typedef Action3 ActionMapIcon; +typedef Action2 ActionEffect; + +struct DefaultStyle : public Command { + Style style; + + DefaultStyle(const Style & s) : Command(COM_DEFAULT, "Default"), style(s) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; + DefaultStyle * clone() const override; +}; + + +/*********** +* STATEMENTS +***********/ + +struct Statement { + StatementType stmType; + int guid; + + Statement(StatementType t) : stmType(t), guid(IFPP_GUID()) {} + virtual std::ostream & printSelf(std::ostream & os, PrintStyle ps) const; + virtual Statement * clone() const = 0; + virtual ~Statement() {} +}; +typedef std::vector FilterIFPP; +std::ostream & print(std::ostream & os, PrintStyle ps, const FilterIFPP & f); + +struct Instruction : public Statement { + std::string what; + + Instruction(const std::string & w) : + Statement(STM_INSTRUCTION), what(w) {} + virtual std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; +}; + +struct InstructionFlush : public Instruction { + InstructionFlush() : Instruction("Flush") {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; + InstructionFlush * clone() const override; +}; + +struct InstructionVersion : public Instruction { + int vMajor, vMinor, vPatch; // Minor and major are reserved by GCC? + + InstructionVersion(int M, int m, int p) : + Instruction("Version"), vMajor(M), vMinor(m), vPatch(p) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; + InstructionVersion * clone() const override; +}; + +struct DefinitionBase : public Statement { + VariableType varType; + std::string varName; + + DefinitionBase(const std::string & vn, VariableType vt) : + Statement(STM_DEFINITION), varType(vt), varName(vn) {} + virtual std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override; +}; + +template +struct Definition : public DefinitionBase { + T value; + + Definition(const std::string & vn, VariableType vt, const T & v) : + DefinitionBase(vn, vt), value(v) {} + std::ostream & printSelf(std::ostream & os, PrintStyle ps) const override { + return DefinitionBase::printSelf(os, ps) << ' ' << value << std::endl << std::endl; + } + Definition * clone() const override { + return new Definition(varName, varType, value); + } +}; + +template<> +struct Definition