diff --git a/Makefile b/Makefile index 514764f79d..73fc34331a 100644 --- a/Makefile +++ b/Makefile @@ -384,7 +384,7 @@ ifeq ($(NODEP),1) $(C_BUILDDIR)/%.o: $(C_SUBDIR)/%.c ifeq (,$(KEEP_TEMPS)) @echo "$(CC1) -o $@ $<" - @$(CPP) $(CPPFLAGS) $< | $(PREPROC) $< charmap.txt -i | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - + @$(CPP) $(CPPFLAGS) $< | $(PREPROC) -i $< charmap.txt | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - else @$(CPP) $(CPPFLAGS) $< -o $(C_BUILDDIR)/$*.i @$(PREPROC) $(C_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(C_BUILDDIR)/$*.s @@ -396,7 +396,7 @@ define C_DEP $1: $2 $$(shell $(SCANINC) -I include -I tools/agbcc/include -I gflib $2) ifeq (,$$(KEEP_TEMPS)) @echo "$$(CC1) -o $$@ $$<" - @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) $$< charmap.txt -i | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - + @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) -i $$< charmap.txt | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - else @$$(CPP) $$(CPPFLAGS) $$< -o $$(C_BUILDDIR)/$3.i @$$(PREPROC) $$(C_BUILDDIR)/$3.i charmap.txt | $$(CC1) $$(CFLAGS) -o $$(C_BUILDDIR)/$3.s @@ -411,7 +411,7 @@ ifeq ($(NODEP),1) $(GFLIB_BUILDDIR)/%.o: $(GFLIB_SUBDIR)/%.c $$(c_dep) ifeq (,$(KEEP_TEMPS)) @echo "$(CC1) -o $@ $<" - @$(CPP) $(CPPFLAGS) $< | $(PREPROC) $< charmap.txt -i | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - + @$(CPP) $(CPPFLAGS) $< | $(PREPROC) -i $< charmap.txt | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - else @$(CPP) $(CPPFLAGS) $< -o $(GFLIB_BUILDDIR)/$*.i @$(PREPROC) $(GFLIB_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(GFLIB_BUILDDIR)/$*.s @@ -423,7 +423,7 @@ define GFLIB_DEP $1: $2 $$(shell $(SCANINC) -I include -I tools/agbcc/include -I gflib $2) ifeq (,$$(KEEP_TEMPS)) @echo "$$(CC1) -o $$@ $$<" - @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) $$< charmap.txt -i | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - + @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) -i $$< charmap.txt | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - else @$$(CPP) $$(CPPFLAGS) $$< -o $$(GFLIB_BUILDDIR)/$3.i @$$(PREPROC) $$(GFLIB_BUILDDIR)/$3.i charmap.txt | $$(CC1) $$(CFLAGS) -o $$(GFLIB_BUILDDIR)/$3.s @@ -436,11 +436,11 @@ endif ifeq ($(NODEP),1) $(C_BUILDDIR)/%.o: $(C_SUBDIR)/%.s - $(PREPROC) $< charmap.txt | $(CPP) -I include - | $(AS) $(ASFLAGS) -o $@ + $(PREPROC) $< charmap.txt | $(CPP) -I include - | $(PREPROC) -i $$< charmap.txt | $(AS) $(ASFLAGS) -o $@ else define SRC_ASM_DATA_DEP $1: $2 $$(shell $(SCANINC) -I include -I "" $2) - $$(PREPROC) $$< charmap.txt | $$(CPP) -I include - | $$(AS) $$(ASFLAGS) -o $$@ + $$(PREPROC) $$< charmap.txt | $$(CPP) -I include - | $$(PREPROC) -ie $$< charmap.txt | $$(AS) $$(ASFLAGS) -o $$@ endef $(foreach src, $(C_ASM_SRCS), $(eval $(call SRC_ASM_DATA_DEP,$(patsubst $(C_SUBDIR)/%.s,$(C_BUILDDIR)/%.o, $(src)),$(src)))) endif @@ -458,7 +458,7 @@ endif ifeq ($(NODEP),1) $(DATA_ASM_BUILDDIR)/%.o: $(DATA_ASM_SUBDIR)/%.s - $(PREPROC) $< charmap.txt | $(CPP) -I include - | $(AS) $(ASFLAGS) -o $@ + $(PREPROC) $< charmap.txt | $(CPP) -I include - | $(PREPROC) -ie $$< charmap.txt | $(AS) $(ASFLAGS) -o $@ else $(foreach src, $(REGULAR_DATA_ASM_SRCS), $(eval $(call SRC_ASM_DATA_DEP,$(patsubst $(DATA_ASM_SUBDIR)/%.s,$(DATA_ASM_BUILDDIR)/%.o, $(src)),$(src)))) endif diff --git a/README.md b/README.md index fabb3f86c6..5a528d988d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ pokeemerald-expansion is a decomp hack base project based off pret's [pokeemeral If you use pokeemerald-expansion in your hack, please add RHH (Rom Hacking Hideout) to your credits list. Optionally, you can list the version used, so it can help players know what features to expect. You can phrase it as the following: ``` -Based off RHH's pokeemerald-expansion v1.8.0 https://github.com/rh-hideout/pokeemerald-expansion/ +Based off RHH's pokeemerald-expansion v1.8.5 https://github.com/rh-hideout/pokeemerald-expansion/ ``` ## What features are included? diff --git a/include/battle.h b/include/battle.h index 0e238e61b9..0b559b1ecb 100644 --- a/include/battle.h +++ b/include/battle.h @@ -218,10 +218,10 @@ struct SpecialStatus u8 statLowered:1; u8 lightningRodRedirected:1; u8 restoredBattlerSprite: 1; - u8 traced:1; u8 faintedHasReplacement:1; u8 focusBanded:1; u8 focusSashed:1; + u8 unused:1; // End of byte u8 sturdied:1; u8 stormDrainRedirected:1; diff --git a/include/battle_util.h b/include/battle_util.h index 30201c167d..ed6a18fdbd 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -32,8 +32,6 @@ enum { ABILITYEFFECT_IMMUNITY, ABILITYEFFECT_SYNCHRONIZE, ABILITYEFFECT_ATK_SYNCHRONIZE, - ABILITYEFFECT_TRACE1, - ABILITYEFFECT_TRACE2, ABILITYEFFECT_MOVE_END_OTHER, ABILITYEFFECT_NEUTRALIZINGGAS, ABILITYEFFECT_FIELD_SPORT, // Only used if B_SPORT_TURNS >= GEN_6 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 8315eeec25..78a9f001ff 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2667,7 +2667,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_UPPER_HAND: - if (predictedMove == MOVE_NONE || IS_MOVE_STATUS(predictedMove) || AI_IsSlower(battlerAtk, battlerDef, move) || GetMovePriority(battlerDef, move) < 1 || GetMovePriority(battlerDef, move) > 3) // Opponent going first or not using priority move + if (predictedMove == MOVE_NONE || IS_MOVE_STATUS(predictedMove) || AI_IsSlower(battlerAtk, battlerDef, move) || GetMovePriority(battlerDef, predictedMove) < 1 || GetMovePriority(battlerDef, predictedMove) > 3) // Opponent going first or not using priority move ADJUST_SCORE(-10); break; case EFFECT_PLACEHOLDER: diff --git a/src/battle_main.c b/src/battle_main.c index 625f2dcd08..0f5f0f22d9 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3854,8 +3854,6 @@ static void TryDoEventsBeforeFirstTurn(void) if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, gBattlerAttacker, 0, 0, 0) != 0) return; } - if (AbilityBattleEffects(ABILITYEFFECT_TRACE1, 0, 0, 0, 0) != 0) - return; // Check all switch in items having effect from the fastest mon to slowest. while (gBattleStruct->switchInItemsCounter < gBattlersCount) { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d77e42d368..9241ddf6be 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -7133,8 +7133,7 @@ bool32 DoSwitchInAbilities(u32 battler) return (TryPrimalReversion(battler) || AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0) || (gBattleWeather & B_WEATHER_ANY && WEATHER_HAS_EFFECT && AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0)) - || (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0)) - || AbilityBattleEffects(ABILITYEFFECT_TRACE2, 0, 0, 0, 0)); + || (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0))); } static void UpdateSentMonFlags(u32 battler) @@ -7282,6 +7281,14 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) gDisableStructs[battler].truantSwitchInHack = 0; + for (i = 0; i < gBattlersCount; i++) + { + if (i != battler + && GetBattlerAbility(i) == ABILITY_TRACE + && AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, 0, 0, 0)) + return TRUE; + } + if (DoSwitchInAbilities(battler) || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler, FALSE)) return TRUE; else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0)) @@ -9249,7 +9256,6 @@ static void Cmd_various(void) case VARIOUS_RESET_SWITCH_IN_ABILITY_BITS: { VARIOUS_ARGS(); - gSpecialStatuses[battler].traced = FALSE; gSpecialStatuses[battler].switchInAbilityDone = FALSE; break; } @@ -9482,7 +9488,6 @@ static void Cmd_various(void) gBattlescriptCurrInstr = cmd->nextInstr; AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0); AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0); - AbilityBattleEffects(ABILITYEFFECT_TRACE2, battler, 0, 0, 0); AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0); return; } diff --git a/src/battle_util.c b/src/battle_util.c index 01e4c9e559..012d7346f1 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4313,6 +4313,50 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 gBattleScripting.battler = battler; switch (gLastUsedAbility) { + case ABILITY_TRACE: + { + u32 chosenTarget; + u32 target1; + u32 target2; + + if (gSpecialStatuses[battler].switchInAbilityDone) + break; + if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_TRACED) + break; + + side = (BATTLE_OPPOSITE(GetBattlerPosition(battler))) & BIT_SIDE; + target1 = GetBattlerAtPosition(side); + target2 = GetBattlerAtPosition(side + BIT_FLANK); + gSpecialStatuses[battler].switchInAbilityDone = TRUE; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0 + && !gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0) + chosenTarget = GetBattlerAtPosition((RandomPercentage(RNG_TRACE, 50) * 2) | side), effect++; + else if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0) + chosenTarget = target1, effect++; + else if (!gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0) + chosenTarget = target2, effect++; + } + else + { + if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0) + chosenTarget = target1, effect++; + } + + if (effect != 0) + { + BattleScriptPushCursorAndCallback(BattleScript_TraceActivatesEnd3); + gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_TRACED; + gBattleStruct->tracedAbility[battler] = gLastUsedAbility = gBattleMons[chosenTarget].ability; + RecordAbilityBattle(chosenTarget, gLastUsedAbility); // Record the opposing battler has this ability + battler = gBattlerAbility = gBattleScripting.battler = battler; + + PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, chosenTarget, gBattlerPartyIndexes[chosenTarget]) + PREPARE_ABILITY_BUFFER(gBattleTextBuff2, gLastUsedAbility) + } + } + break; case ABILITY_IMPOSTER: if (IsBattlerAlive(BATTLE_OPPOSITE(battler)) && !(gBattleMons[BATTLE_OPPOSITE(battler)].status2 & (STATUS2_TRANSFORMED | STATUS2_SUBSTITUTE)) @@ -4650,13 +4694,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; - case ABILITY_TRACE: - if (!(gSpecialStatuses[battler].traced)) - { - gBattleResources->flags->flags[battler] |= RESOURCE_FLAG_TRACED; - gSpecialStatuses[battler].traced = TRUE; - } - break; case ABILITY_CLOUD_NINE: case ABILITY_AIR_LOCK: if (!gSpecialStatuses[battler].switchInAbilityDone) @@ -6132,48 +6169,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } } break; - case ABILITYEFFECT_TRACE1: - case ABILITYEFFECT_TRACE2: - for (i = 0; i < gBattlersCount; i++) - { - if (gBattleMons[i].ability == ABILITY_TRACE && (gBattleResources->flags->flags[i] & RESOURCE_FLAG_TRACED)) - { - u32 chosenTarget; - u32 side = (BATTLE_OPPOSITE(GetBattlerPosition(i))) & BIT_SIDE; // side of the opposing Pokémon - u32 target1 = GetBattlerAtPosition(side); - u32 target2 = GetBattlerAtPosition(side + BIT_FLANK); - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0 - && !gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0) - chosenTarget = GetBattlerAtPosition((RandomPercentage(RNG_TRACE, 50) * 2) | side), effect++; - else if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0) - chosenTarget = target1, effect++; - else if (!gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0) - chosenTarget = target2, effect++; - } - else - { - if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0) - chosenTarget = target1, effect++; - } - - if (effect != 0) - { - BattleScriptPushCursorAndCallback(BattleScript_TraceActivatesEnd3); - gBattleResources->flags->flags[i] &= ~RESOURCE_FLAG_TRACED; - gBattleStruct->tracedAbility[i] = gLastUsedAbility = gBattleMons[chosenTarget].ability; - RecordAbilityBattle(chosenTarget, gLastUsedAbility); // Record the opposing battler has this ability - battler = gBattlerAbility = gBattleScripting.battler = i; - - PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, chosenTarget, gBattlerPartyIndexes[chosenTarget]) - PREPARE_ABILITY_BUFFER(gBattleTextBuff2, gLastUsedAbility) - break; - } - } - } - break; case ABILITYEFFECT_NEUTRALIZINGGAS: // Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards for (i = 0; i < gBattlersCount; i++) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 280a04cd4d..e79f804bb1 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -14718,6 +14718,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .metronomeBanned = TRUE, .additionalEffects = ADDITIONAL_EFFECTS({ .moveEffect = B_UPDATED_MOVE_DATA >= GEN_7 ? MOVE_EFFECT_DEF_PLUS_2: MOVE_EFFECT_DEF_PLUS_1, + .self = TRUE, .chance = 50, }), .contestEffect = CONTEST_EFFECT_USER_MORE_EASILY_STARTLED, diff --git a/src/data/pokemon/level_up_learnsets/gen_9.h b/src/data/pokemon/level_up_learnsets/gen_9.h index 21b0a96112..0db97107a2 100644 --- a/src/data/pokemon/level_up_learnsets/gen_9.h +++ b/src/data/pokemon/level_up_learnsets/gen_9.h @@ -12801,7 +12801,6 @@ static const struct LevelUpMove sBasculinWhiteStripedLevelUpLearnset[] = { }; static const struct LevelUpMove sBasculegionLevelUpLearnset[] = { - LEVEL_UP_MOVE( 0, MOVE_DIRE_CLAW), LEVEL_UP_MOVE( 1, MOVE_SHADOW_BALL), LEVEL_UP_MOVE( 1, MOVE_TAIL_WHIP), LEVEL_UP_MOVE( 1, MOVE_WATER_GUN), diff --git a/test/battle/ability/trace.c b/test/battle/ability/trace.c index bb72f38860..acc49bcf13 100644 --- a/test/battle/ability/trace.c +++ b/test/battle/ability/trace.c @@ -79,3 +79,20 @@ SINGLE_BATTLE_TEST("Trace will copy an opponent's ability whenever it has the ch MESSAGE("Ralts TRACED Foe Torchic's Blaze!"); } } + +DOUBLE_BATTLE_TEST("Trace respects the turn order") +{ + GIVEN { + PLAYER(SPECIES_DEOXYS_SPEED) { Speed(40); Ability(ABILITY_PRESSURE); } + PLAYER(SPECIES_GARDEVOIR) { Speed(20); Ability(ABILITY_TRACE); } + OPPONENT(SPECIES_HIPPOWDON) { Speed(10); Ability(ABILITY_SAND_STREAM); } + OPPONENT(SPECIES_DEOXYS_SPEED) { Speed(30); Ability(ABILITY_PRESSURE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_PRESSURE); + ABILITY_POPUP(opponentRight, ABILITY_PRESSURE); + ABILITY_POPUP(playerRight, ABILITY_TRACE); + ABILITY_POPUP(opponentLeft, ABILITY_SAND_STREAM); + } +} diff --git a/test/battle/move_effect/upper_hand.c b/test/battle/move_effect/upper_hand.c index 04f2b85225..6a041182fe 100644 --- a/test/battle/move_effect/upper_hand.c +++ b/test/battle/move_effect/upper_hand.c @@ -116,3 +116,19 @@ SINGLE_BATTLE_TEST("Upper Hand is boosted by Sheer Force") HP_BAR(player); } } + +AI_SINGLE_BATTLE_TEST("AI won't use Upper Hand unless it has seen a priority move") +{ + u16 move; + PARAMETRIZE { move = MOVE_TACKLE; } + PARAMETRIZE { move = MOVE_QUICK_ATTACK; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + ASSUME(gMovesInfo[MOVE_QUICK_ATTACK].priority == 1); + PLAYER(SPECIES_WOBBUFFET) {Moves(move); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_UPPER_HAND, MOVE_KARATE_CHOP); } + } WHEN { + TURN { MOVE(player, move); EXPECT_MOVE(opponent, MOVE_KARATE_CHOP); } + TURN { MOVE(player, move); EXPECT_MOVE(opponent, move == MOVE_QUICK_ATTACK ? MOVE_UPPER_HAND : MOVE_KARATE_CHOP); } + } +} diff --git a/tools/preproc/Makefile b/tools/preproc/Makefile index 1507c973f2..606318944c 100644 --- a/tools/preproc/Makefile +++ b/tools/preproc/Makefile @@ -3,10 +3,10 @@ CXX ?= g++ CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch -Werror SRCS := asm_file.cpp c_file.cpp charmap.cpp preproc.cpp string_parser.cpp \ - utf8.cpp + utf8.cpp io.cpp HEADERS := asm_file.h c_file.h char_util.h charmap.h preproc.h string_parser.h \ - utf8.h + utf8.h io.h ifeq ($(OS),Windows_NT) EXE := .exe diff --git a/tools/preproc/asm_file.cpp b/tools/preproc/asm_file.cpp index 04a7410e00..ca8b8cc4ae 100644 --- a/tools/preproc/asm_file.cpp +++ b/tools/preproc/asm_file.cpp @@ -27,33 +27,12 @@ #include "utf8.h" #include "string_parser.h" #include "../../gflib/characters.h" +#include "io.h" -AsmFile::AsmFile(std::string filename) : m_filename(filename) +AsmFile::AsmFile(std::string filename, bool isStdin, bool doEnum) : m_filename(filename) { - FILE *fp = std::fopen(filename.c_str(), "rb"); - - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str()); - - std::fseek(fp, 0, SEEK_END); - - m_size = std::ftell(fp); - - if (m_size < 0) - FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str()); - else if (m_size == 0) - return; // Empty file - - m_buffer = new char[m_size + 1]; - - std::rewind(fp); - - if (std::fread(m_buffer, m_size, 1, fp) != 1) - FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str()); - - m_buffer[m_size] = 0; - - std::fclose(fp); + m_buffer = ReadFileToBuffer(filename.c_str(), isStdin, &m_size); + m_doEnum = doEnum; m_pos = 0; m_lineNum = 1; @@ -65,6 +44,7 @@ AsmFile::AsmFile(std::string filename) : m_filename(filename) AsmFile::AsmFile(AsmFile&& other) : m_filename(std::move(other.m_filename)) { m_buffer = other.m_buffer; + m_doEnum = other.m_doEnum; m_pos = other.m_pos; m_size = other.m_size; m_lineNum = other.m_lineNum; @@ -174,6 +154,8 @@ Directive AsmFile::GetDirective() return Directive::String; else if (CheckForDirective(".braille")) return Directive::Braille; + else if (CheckForDirective("enum")) + return Directive::Enum; else return Directive::Unknown; } @@ -527,6 +509,70 @@ void AsmFile::OutputLine() } } +// parses an assumed C `enum`. Returns false if `enum { ...` is not matched +bool AsmFile::ParseEnum() +{ + if (!m_doEnum) + return false; + + long fallbackPosition = m_pos; + std::string headerFilename = ""; + long currentHeaderLine = SkipWhitespaceAndEol(); + std::string enumName = ReadIdentifier(); + currentHeaderLine += SkipWhitespaceAndEol(); + long enumCounter = 0; + long symbolCount = 0; + + if (m_buffer[m_pos] != '{') // assume assembly macro, otherwise assume enum and report errors accordingly + { + m_pos = fallbackPosition - 4; + return false; + } + + currentHeaderLine += FindLastLineNumber(headerFilename); + m_pos++; + for (;;) + { + currentHeaderLine += SkipWhitespaceAndEol(); + std::string currentIdentName = ReadIdentifier(); + if (!currentIdentName.empty()) + { + std::printf("# %ld \"%s\"\n", currentHeaderLine, headerFilename.c_str()); + currentHeaderLine += SkipWhitespaceAndEol(); + if (m_buffer[m_pos] == '=') + { + m_pos++; + currentHeaderLine += SkipWhitespaceAndEol(); + enumCounter = ReadInteger(headerFilename, currentHeaderLine); + currentHeaderLine += SkipWhitespaceAndEol(); + } + std::printf(".equiv %s, %ld\n", currentIdentName.c_str(), enumCounter); + enumCounter++; + symbolCount++; + } + else if (symbolCount == 0) + { + RaiseError("%s:%ld: empty enum is invalid", headerFilename.c_str(), currentHeaderLine); + } + + if (m_buffer[m_pos] != ',') + { + currentHeaderLine += SkipWhitespaceAndEol(); + if (m_buffer[m_pos++] == '}' && m_buffer[m_pos++] == ';') + { + ExpectEmptyRestOfLine(); + break; + } + else + { + RaiseError("unterminated enum from included file %s:%ld", headerFilename.c_str(), currentHeaderLine); + } + } + m_pos++; + } + return true; +} + // Asserts that the rest of the line is empty and moves to the next one. void AsmFile::ExpectEmptyRestOfLine() { @@ -599,3 +645,130 @@ void AsmFile::RaiseWarning(const char* format, ...) { DO_REPORT("warning"); } + +// Skips Whitespace including newlines and returns the amount of newlines skipped +int AsmFile::SkipWhitespaceAndEol() +{ + int newlines = 0; + while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ' || m_buffer[m_pos] == '\n') + { + if (m_buffer[m_pos] == '\n') + newlines++; + m_pos++; + } + return newlines; +} + +// returns the last line indicator and its corresponding file name without modifying the token index +int AsmFile::FindLastLineNumber(std::string& filename) +{ + long pos = m_pos; + long linebreaks = 0; + while (m_buffer[pos] != '#' && pos >= 0) + { + if (m_buffer[pos] == '\n') + linebreaks++; + pos--; + } + + if (pos < 0) + RaiseError("line indicator for header file not found before `enum`"); + + pos++; + while (m_buffer[pos] == ' ' || m_buffer[pos] == '\t') + pos++; + + if (!IsAsciiDigit(m_buffer[pos])) + RaiseError("malformatted line indicator found before `enum`, expected line number"); + + unsigned n = 0; + int digit = 0; + while ((digit = ConvertDigit(m_buffer[pos++], 10)) != -1) + n = 10 * n + digit; + + while (m_buffer[pos] == ' ' || m_buffer[pos] == '\t') + pos++; + + if (m_buffer[pos++] != '"') + RaiseError("malformatted line indicator found before `enum`, expected filename"); + + while (m_buffer[pos] != '"') + { + unsigned char c = m_buffer[pos++]; + + if (c == 0) + { + if (pos >= m_size) + RaiseError("unexpected EOF in line indicator"); + else + RaiseError("unexpected null character in line indicator"); + } + + if (!IsAsciiPrintable(c)) + RaiseError("unexpected character '\\x%02X' in line indicator", c); + + if (c == '\\') + { + c = m_buffer[pos]; + RaiseError("unexpected escape '\\%c' in line indicator", c); + } + + filename += c; + } + + return n + linebreaks - 1; +} + +std::string AsmFile::ReadIdentifier() +{ + long start = m_pos; + if (!IsIdentifierStartingChar(m_buffer[m_pos])) + return std::string(); + + m_pos++; + + while (IsIdentifierChar(m_buffer[m_pos])) + m_pos++; + + return std::string(&m_buffer[start], m_pos - start); +} + +long AsmFile::ReadInteger(std::string filename, long line) +{ + bool negate = false; + int radix = 10; + if (!IsAsciiDigit(m_buffer[m_pos])) + { + if (m_buffer[m_pos++] == '-') + negate = true; + else + RaiseError("expected number in included file %s:%ld", filename.c_str(), line); + } + + if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'x') + { + radix = 16; + m_pos += 2; + } + else if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'b') + { + radix = 2; + m_pos += 2; + } + else if (m_buffer[m_pos] == '0' && IsAsciiDigit(m_buffer[m_pos+1])) + { + radix = 8; + m_pos++; + } + + long n = 0; + int digit; + + while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1) + { + n = n * radix + digit; + m_pos++; + } + + return negate ? -n : n; +} diff --git a/tools/preproc/asm_file.h b/tools/preproc/asm_file.h index 29435f76a4..33e6ce5c49 100644 --- a/tools/preproc/asm_file.h +++ b/tools/preproc/asm_file.h @@ -31,13 +31,14 @@ enum class Directive Include, String, Braille, + Enum, Unknown }; class AsmFile { public: - AsmFile(std::string filename); + AsmFile(std::string filename, bool isStdin, bool doEnum); AsmFile(AsmFile&& other); AsmFile(const AsmFile&) = delete; ~AsmFile(); @@ -49,9 +50,11 @@ public: bool IsAtEnd(); void OutputLine(); void OutputLocation(); + bool ParseEnum(); private: char* m_buffer; + bool m_doEnum; long m_pos; long m_size; long m_lineNum; @@ -68,6 +71,10 @@ private: void RaiseError(const char* format, ...); void RaiseWarning(const char* format, ...); void VerifyStringLength(int length); + int SkipWhitespaceAndEol(); + int FindLastLineNumber(std::string& filename); + std::string ReadIdentifier(); + long ReadInteger(std::string filename, long line); }; #endif // ASM_FILE_H diff --git a/tools/preproc/c_file.cpp b/tools/preproc/c_file.cpp index 3aabf38a72..4b40d18bc8 100644 --- a/tools/preproc/c_file.cpp +++ b/tools/preproc/c_file.cpp @@ -30,56 +30,16 @@ #include "char_util.h" #include "utf8.h" #include "string_parser.h" +#include "io.h" CFile::CFile(const char * filenameCStr, bool isStdin) { - FILE *fp; - - if (isStdin) { - fp = stdin; + if (isStdin) m_filename = std::string{"/"}.append(filenameCStr); - } else { - fp = std::fopen(filenameCStr, "rb"); + else m_filename = std::string(filenameCStr); - } - std::string& filename = m_filename; - - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str()); - - m_size = 0; - m_buffer = (char *)malloc(CHUNK_SIZE + 1); - if (m_buffer == NULL) { - FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename.c_str()); - } - - std::size_t numAllocatedBytes = CHUNK_SIZE + 1; - std::size_t bufferOffset = 0; - std::size_t count; - - while ((count = std::fread(m_buffer + bufferOffset, 1, CHUNK_SIZE, fp)) != 0) { - if (!std::ferror(fp)) { - m_size += count; - - if (std::feof(fp)) { - break; - } - - numAllocatedBytes += CHUNK_SIZE; - bufferOffset += CHUNK_SIZE; - m_buffer = (char *)realloc(m_buffer, numAllocatedBytes); - if (m_buffer == NULL) { - FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename.c_str()); - } - } else { - FATAL_ERROR("Failed to read \"%s\". (error: %s)", filename.c_str(), std::strerror(errno)); - } - } - - m_buffer[m_size] = 0; - - std::fclose(fp); + m_buffer = ReadFileToBuffer(filenameCStr, isStdin, &m_size); m_pos = 0; m_lineNum = 1; diff --git a/tools/preproc/c_file.h b/tools/preproc/c_file.h index 49e633a18d..c40c33c962 100644 --- a/tools/preproc/c_file.h +++ b/tools/preproc/c_file.h @@ -56,6 +56,4 @@ private: void RaiseWarning(const char* format, ...); }; -#define CHUNK_SIZE 4096 - #endif // C_FILE_H diff --git a/tools/preproc/io.cpp b/tools/preproc/io.cpp new file mode 100644 index 0000000000..321676180d --- /dev/null +++ b/tools/preproc/io.cpp @@ -0,0 +1,51 @@ +#include "preproc.h" +#include "io.h" +#include +#include +#include + +char *ReadFileToBuffer(const char *filename, bool isStdin, long *size) +{ + FILE *fp; + if (isStdin) + fp = stdin; + else + fp = std::fopen(filename, "rb"); + + if (fp == NULL) + FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename); + + *size = 0; + char *buffer = (char *)malloc(CHUNK_SIZE + 1); + if (buffer == NULL) { + FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename); + } + + std::size_t numAllocatedBytes = CHUNK_SIZE + 1; + std::size_t bufferOffset = 0; + std::size_t count; + + while ((count = std::fread(buffer + bufferOffset, 1, CHUNK_SIZE, fp)) != 0) { + if (!std::ferror(fp)) { + *size += count; + + if (std::feof(fp)) { + break; + } + + numAllocatedBytes += CHUNK_SIZE; + bufferOffset += CHUNK_SIZE; + buffer = (char *)realloc(buffer, numAllocatedBytes); + if (buffer == NULL) { + FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename); + } + } else { + FATAL_ERROR("Failed to read \"%s\". (error: %s)", filename, std::strerror(errno)); + } + } + + buffer[*size] = 0; + + std::fclose(fp); + return buffer; +} diff --git a/tools/preproc/io.h b/tools/preproc/io.h new file mode 100644 index 0000000000..ac4e91051f --- /dev/null +++ b/tools/preproc/io.h @@ -0,0 +1,8 @@ +#ifndef IO_H_ +#define IO_H_ + +#define CHUNK_SIZE 4096 + +char *ReadFileToBuffer(const char *filename, bool isStdin, long *size); + +#endif // IO_H_ diff --git a/tools/preproc/preproc.cpp b/tools/preproc/preproc.cpp index eb2d4c8a23..20c2de51b6 100644 --- a/tools/preproc/preproc.cpp +++ b/tools/preproc/preproc.cpp @@ -20,11 +20,14 @@ #include #include +#include #include "preproc.h" #include "asm_file.h" #include "c_file.h" #include "charmap.h" +static void UsageAndExit(const char *program); + Charmap* g_charmap; void PrintAsmBytes(unsigned char *s, int length) @@ -43,11 +46,12 @@ void PrintAsmBytes(unsigned char *s, int length) } } -void PreprocAsmFile(std::string filename) +void PreprocAsmFile(std::string filename, bool isStdin, bool doEnum) { std::stack stack; - stack.push(AsmFile(filename)); + stack.push(AsmFile(filename, isStdin, doEnum)); + std::printf("# 1 \"%s\"\n", filename.c_str()); for (;;) { @@ -66,7 +70,7 @@ void PreprocAsmFile(std::string filename) switch (directive) { case Directive::Include: - stack.push(AsmFile(stack.top().ReadPath())); + stack.push(AsmFile(stack.top().ReadPath(), false, doEnum)); stack.top().OutputLocation(); break; case Directive::String: @@ -83,6 +87,12 @@ void PreprocAsmFile(std::string filename) PrintAsmBytes(s, length); break; } + case Directive::Enum: + { + if (!stack.top().ParseEnum()) + stack.top().OutputLine(); + break; + } case Directive::Unknown: { std::string globalLabel = stack.top().GetGlobalLabel(); @@ -109,9 +119,9 @@ void PreprocCFile(const char * filename, bool isStdin) cFile.Preproc(); } -char* GetFileExtension(char* filename) +const char* GetFileExtension(const char* filename) { - char* extension = filename; + const char* extension = filename; while (*extension != 0) extension++; @@ -130,35 +140,64 @@ char* GetFileExtension(char* filename) return extension; } +static void UsageAndExit(const char *program) +{ + std::fprintf(stderr, "Usage: %s [-i] [-e] SRC_FILE CHARMAP_FILE\nwhere -i denotes if input is from stdin\n -e enables enum handling\n", program); + std::exit(EXIT_FAILURE); +} + int main(int argc, char **argv) { - if (argc < 3 || argc > 4) + int opt; + const char *source = NULL; + const char *charmap = NULL; + bool isStdin = false; + bool doEnum = false; + + /* preproc [-i] [-e] SRC_FILE CHARMAP_FILE */ + while ((opt = getopt(argc, argv, "ie")) != -1) { - std::fprintf(stderr, "Usage: %s SRC_FILE CHARMAP_FILE [-i]\nwhere -i denotes if input is from stdin\n", argv[0]); - return 1; + switch (opt) + { + case 'i': + isStdin = true; + break; + case 'e': + doEnum = true; + break; + default: + UsageAndExit(argv[0]); + break; + } } - g_charmap = new Charmap(argv[2]); + if (optind + 2 != argc) + UsageAndExit(argv[0]); - char* extension = GetFileExtension(argv[1]); + source = argv[optind + 0]; + charmap = argv[optind + 1]; + + g_charmap = new Charmap(charmap); + + const char* extension = GetFileExtension(source); if (!extension) FATAL_ERROR("\"%s\" has no file extension.\n", argv[1]); if ((extension[0] == 's') && extension[1] == 0) - PreprocAsmFile(argv[1]); - else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0) { - if (argc == 4) { - if (argv[3][0] == '-' && argv[3][1] == 'i' && argv[3][2] == '\0') { - PreprocCFile(argv[1], true); - } else { - FATAL_ERROR("unknown argument flag \"%s\".\n", argv[3]); - } - } else { - PreprocCFile(argv[1], false); - } - } else + { + PreprocAsmFile(source, isStdin, doEnum); + } + else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0) + { + if (doEnum) + FATAL_ERROR("-e is invalid for C sources\n"); + PreprocCFile(source, isStdin); + } + else + { FATAL_ERROR("\"%s\" has an unknown file extension of \"%s\".\n", argv[1], extension); + } return 0; }