Merge branch '_RHH/master' into _RHH/upcoming

# Conflicts:
#	include/battle_util.h
#	src/battle_ai_main.c
This commit is contained in:
Eduardo Quezada 2024-07-14 22:44:00 -04:00
commit 2b40e79ac3
20 changed files with 429 additions and 163 deletions

View file

@ -384,7 +384,7 @@ ifeq ($(NODEP),1)
$(C_BUILDDIR)/%.o: $(C_SUBDIR)/%.c
ifeq (,$(KEEP_TEMPS))
@echo "$(CC1) <flags> -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) <flags> -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) <flags> -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) <flags> -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

View file

@ -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?

View file

@ -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;

View file

@ -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

View file

@ -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:

View file

@ -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)
{

View file

@ -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;
}

View file

@ -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++)

View file

@ -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,

View file

@ -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),

View file

@ -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);
}
}

View file

@ -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); }
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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{"<stdin>/"}.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;

View file

@ -56,6 +56,4 @@ private:
void RaiseWarning(const char* format, ...);
};
#define CHUNK_SIZE 4096
#endif // C_FILE_H

51
tools/preproc/io.cpp Normal file
View file

@ -0,0 +1,51 @@
#include "preproc.h"
#include "io.h"
#include <string>
#include <cerrno>
#include <cstring>
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;
}

8
tools/preproc/io.h Normal file
View file

@ -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_

View file

@ -20,11 +20,14 @@
#include <string>
#include <stack>
#include <unistd.h>
#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<AsmFile> 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;
}